diff --git a/Campus-iOS.xcodeproj/project.pbxproj b/Campus-iOS.xcodeproj/project.pbxproj index 00d7ccd7..3713ef7d 100644 --- a/Campus-iOS.xcodeproj/project.pbxproj +++ b/Campus-iOS.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 08DFB97528664CFC00E357DF /* TuitionDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DFB97428664CFC00E357DF /* TuitionDetailsView.swift */; }; 08DFB9772866506900E357DF /* WidgetFrameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DFB9762866506900E357DF /* WidgetFrameView.swift */; }; 08DFB97928666AD900E357DF /* CafeteriaWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DFB97828666AD900E357DF /* CafeteriaWidgetView.swift */; }; + 08DFB97D2867800C00E357DF /* CafeteriaWidgetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DFB97C2867800C00E357DF /* CafeteriaWidgetViewModel.swift */; }; 08DFB97F2867AC9200E357DF /* StudyRoomWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DFB97E2867AC9200E357DF /* StudyRoomWidgetView.swift */; }; 08DFB9812867ACB600E357DF /* StudyRoomWidgetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DFB9802867ACB600E357DF /* StudyRoomWidgetViewModel.swift */; }; 08FAFD15287DC484006A0E27 /* CalendarWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FAFD14287DC484006A0E27 /* CalendarWidgetView.swift */; }; @@ -45,86 +46,157 @@ 08FAFD292898B6C8006A0E27 /* SpatioTemporalStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FAFD282898B6C8006A0E27 /* SpatioTemporalStrategy.swift */; }; 100803462764E2C50013ED0E /* ProfileToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 100803452764E2C50013ED0E /* ProfileToolbar.swift */; }; 100803482764E37A0013ED0E /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 100803472764E37A0013ED0E /* ProfileView.swift */; }; - 1F04F16E297A9A700085F273 /* CalendarService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F04F16D297A9A700085F273 /* CalendarService.swift */; }; - 1F04F171297AA5F40085F273 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F04F170297AA5F30085F273 /* Service.swift */; }; - 1F04F173297AD41B0085F273 /* CalendarEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F04F172297AD41B0085F273 /* CalendarEvent.swift */; }; - 1F04F175297AD4280085F273 /* CalendarScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F04F174297AD4280085F273 /* CalendarScreen.swift */; }; - 1F04F179297AED150085F273 /* LectureSearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F04F178297AED150085F273 /* LectureSearchService.swift */; }; - 1F04F17F297BDF1E0085F273 /* PersonSearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F04F17E297BDF1E0085F273 /* PersonSearchService.swift */; }; - 1F04F183297C3EF70085F273 /* PersonDetailedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F04F182297C3EF70085F273 /* PersonDetailedScreen.swift */; }; - 1F04F185297C3F990085F273 /* PersonDetailedService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F04F184297C3F990085F273 /* PersonDetailedService.swift */; }; - 1F04F18A297C85120085F273 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F04F189297C85120085F273 /* Token.swift */; }; - 1F04F18C297C85190085F273 /* Confirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F04F18B297C85190085F273 /* Confirmation.swift */; }; - 1F183A172979D19000B5D22D /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F183A162979D19000B5D22D /* APIError.swift */; }; - 1F189E8D29968CE50056BBD8 /* TUMOnlineAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189E8C29968CE50056BBD8 /* TUMOnlineAPI.swift */; }; - 1F189E8F29968CFC0056BBD8 /* TUMCabeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189E8E29968CFC0056BBD8 /* TUMCabeAPI.swift */; }; - 1F189E9129968D130056BBD8 /* EatAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189E9029968D130056BBD8 /* EatAPI.swift */; }; - 1F189E9329968D260056BBD8 /* TUMDevAppAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189E9229968D260056BBD8 /* TUMDevAppAPI.swift */; }; - 1F189E9529968D330056BBD8 /* TUMSexyAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189E9429968D330056BBD8 /* TUMSexyAPI.swift */; }; - 1F189E9729968D490056BBD8 /* MVGAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189E9629968D490056BBD8 /* MVGAPI.swift */; }; - 1F189E9A29968D790056BBD8 /* TUMOnlineAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189E9929968D790056BBD8 /* TUMOnlineAPIError.swift */; }; - 1F189E9C29968D880056BBD8 /* TUMCabeAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189E9B29968D880056BBD8 /* TUMCabeAPIError.swift */; }; - 1F189E9E29968D9B0056BBD8 /* EatAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189E9D29968D9B0056BBD8 /* EatAPIError.swift */; }; - 1F189EA029968DA90056BBD8 /* TUMDevAppAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189E9F29968DA90056BBD8 /* TUMDevAppAPIError.swift */; }; - 1F189EA229968DB90056BBD8 /* TUMSexyAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189EA129968DB90056BBD8 /* TUMSexyAPIError.swift */; }; - 1F189EA429968DE60056BBD8 /* MVGAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189EA329968DE60056BBD8 /* MVGAPIError.swift */; }; - 1F189EA729968E5C0056BBD8 /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F189EA629968E5C0056BBD8 /* API.swift */; }; + 1ED0F9D52A6534D400A27A75 /* GradesStudyProgramView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED0F9D42A6534D400A27A75 /* GradesStudyProgramView.swift */; }; + 1ED0F9D72A6534DE00A27A75 /* GradesSemesterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED0F9D62A6534DE00A27A75 /* GradesSemesterView.swift */; }; + 1ED0F9D92A65351C00A27A75 /* AverageGradesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED0F9D82A65351C00A27A75 /* AverageGradesService.swift */; }; + 1ED0F9DB2A65353000A27A75 /* AverageGrade.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED0F9DA2A65353000A27A75 /* AverageGrade.swift */; }; + 1F0E8EBB293E22FF006E8BB9 /* DataTypeClassifierV2.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = 1F0E8EBA293E22FF006E8BB9 /* DataTypeClassifierV2.mlmodel */; }; + 1F0F396A2A058AAA00E55FCB /* Grade+PreviewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0F39692A058AAA00E55FCB /* Grade+PreviewData.swift */; }; + 1F0F396C2A058AEA00E55FCB /* News+PreviewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0F396B2A058AEA00E55FCB /* News+PreviewData.swift */; }; + 1F0F396F2A058B5000E55FCB /* Movie+PreviewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0F396E2A058B5000E55FCB /* Movie+PreviewData.swift */; }; + 1F0F39712A058B9F00E55FCB /* StudyRoomApiResponse+PreviewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0F39702A058B9F00E55FCB /* StudyRoomApiResponse+PreviewData.swift */; }; + 1F0F39732A05913F00E55FCB /* Cafeteria+PreviewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0F39722A05913F00E55FCB /* Cafeteria+PreviewData.swift */; }; + 1F0F39752A05917400E55FCB /* CalendarEvent+PreviewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0F39742A05917400E55FCB /* CalendarEvent+PreviewData.swift */; }; + 1F0F39772A05975200E55FCB /* SearchState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0F39762A05975200E55FCB /* SearchState.swift */; }; + 1F1F42B32999331F00A0D0B7 /* SearchResultBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F42B22999331F00A0D0B7 /* SearchResultBarView.swift */; }; 1F2068DC28FD6E2800DBDF67 /* LoginViewModel+LoginState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2068DB28FD6E2800DBDF67 /* LoginViewModel+LoginState.swift */; }; 1F2068DE28FD731200DBDF67 /* LoginViewModel+TokenState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2068DD28FD731200DBDF67 /* LoginViewModel+TokenState.swift */; }; 1F2068E228FD73C400DBDF67 /* TokenPermissionsViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2068E128FD73C400DBDF67 /* TokenPermissionsViewModel+State.swift */; }; 1F2068E428FD73CB00DBDF67 /* TokenPermissionsViewModel+PermissionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2068E328FD73CB00DBDF67 /* TokenPermissionsViewModel+PermissionType.swift */; }; + 1F26156229D9E8BD0062B8E5 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26155E29D9E8BD0062B8E5 /* APIError.swift */; }; + 1F26156329D9E8BD0062B8E5 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26155F29D9E8BD0062B8E5 /* Service.swift */; }; + 1F26156429D9E8BD0062B8E5 /* MainAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26156029D9E8BD0062B8E5 /* MainAPI.swift */; }; + 1F26156529D9E8BD0062B8E5 /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26156129D9E8BD0062B8E5 /* API.swift */; }; + 1F26157E29D9E8DD0062B8E5 /* TUMDevAppAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26156729D9E8DC0062B8E5 /* TUMDevAppAPIOld.swift */; }; + 1F26157F29D9E8DD0062B8E5 /* EatAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26156829D9E8DC0062B8E5 /* EatAPIOld.swift */; }; + 1F26158029D9E8DD0062B8E5 /* MVGAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26156929D9E8DC0062B8E5 /* MVGAPIOld.swift */; }; + 1F26158129D9E8DD0062B8E5 /* CampusOnlineAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26156A29D9E8DC0062B8E5 /* CampusOnlineAPIOld.swift */; }; + 1F26158229D9E8DD0062B8E5 /* TUMCabeAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26156B29D9E8DC0062B8E5 /* TUMCabeAPIOld.swift */; }; + 1F26158329D9E8DD0062B8E5 /* TUMSexyAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26156C29D9E8DC0062B8E5 /* TUMSexyAPIOld.swift */; }; + 1F26158429D9E8DD0062B8E5 /* TUMOnlineAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26156D29D9E8DC0062B8E5 /* TUMOnlineAPIOld.swift */; }; + 1F26158529D9E8DD0062B8E5 /* APIResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26156E29D9E8DC0062B8E5 /* APIResponse.swift */; }; + 1F26158629D9E8DD0062B8E5 /* NetworkingAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26156F29D9E8DC0062B8E5 /* NetworkingAPIOld.swift */; }; + 1F26158729D9E8DD0062B8E5 /* TUMDevAppAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26157129D9E8DC0062B8E5 /* TUMDevAppAPIError.swift */; }; + 1F26158829D9E8DD0062B8E5 /* TUMOnlineAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26157229D9E8DC0062B8E5 /* TUMOnlineAPIError.swift */; }; + 1F26158929D9E8DD0062B8E5 /* EatAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26157329D9E8DC0062B8E5 /* EatAPIError.swift */; }; + 1F26158A29D9E8DD0062B8E5 /* TUMCabeAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26157429D9E8DC0062B8E5 /* TUMCabeAPIError.swift */; }; + 1F26158B29D9E8DD0062B8E5 /* MVGAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26157529D9E8DC0062B8E5 /* MVGAPIError.swift */; }; + 1F26158C29D9E8DD0062B8E5 /* TUMSexyAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26157629D9E8DC0062B8E5 /* TUMSexyAPIError.swift */; }; + 1F26158D29D9E8DD0062B8E5 /* TUMSexyAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26157829D9E8DC0062B8E5 /* TUMSexyAPI.swift */; }; + 1F26158E29D9E8DD0062B8E5 /* EatAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26157929D9E8DC0062B8E5 /* EatAPI.swift */; }; + 1F26158F29D9E8DD0062B8E5 /* TUMDevAppAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26157A29D9E8DC0062B8E5 /* TUMDevAppAPI.swift */; }; + 1F26159029D9E8DD0062B8E5 /* MVGAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26157B29D9E8DC0062B8E5 /* MVGAPI.swift */; }; + 1F26159129D9E8DD0062B8E5 /* TUMOnlineAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26157C29D9E8DC0062B8E5 /* TUMOnlineAPI.swift */; }; + 1F26159229D9E8DD0062B8E5 /* TUMCabeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26157D29D9E8DC0062B8E5 /* TUMCabeAPI.swift */; }; + 1F26159529D9E8F10062B8E5 /* ProfileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26159429D9E8F10062B8E5 /* ProfileService.swift */; }; + 1F26159829D9E9010062B8E5 /* CrashlyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26159729D9E9010062B8E5 /* CrashlyticsService.swift */; }; + 1F26159A29DA00EB0062B8E5 /* MensaCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26159929DA00EB0062B8E5 /* MensaCategory.swift */; }; + 1F26159D29DA015D0062B8E5 /* DishService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26159B29DA015D0062B8E5 /* DishService.swift */; }; + 1F26159E29DA015D0062B8E5 /* MealPlanService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26159C29DA015D0062B8E5 /* MealPlanService.swift */; }; + 1F2615A029DA021D0062B8E5 /* StudyRoomDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F26159F29DA021D0062B8E5 /* StudyRoomDetailsScreen.swift */; }; + 1F2615A229DA02E10062B8E5 /* MealPlanScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2615A129DA02E10062B8E5 /* MealPlanScreen.swift */; }; + 1F2615A429DA02FC0062B8E5 /* DishViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2615A329DA02FC0062B8E5 /* DishViewModel.swift */; }; 1F33B2ED282B084100C898E4 /* MockGradesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F33B2EC282B084100C898E4 /* MockGradesViewModel.swift */; }; + 1F36EC8229377CD300349FC1 /* DataTypeClassifierV1.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = 1F36EC8129377CD300349FC1 /* DataTypeClassifierV1.mlmodel */; }; + 1F488336296EF0CD0002CE77 /* DataTypeClassifierV3.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = 1F488335296EF0CD0002CE77 /* DataTypeClassifierV3.mlmodel */; }; 1F4C836228300306006971C0 /* MapViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4C836128300306006971C0 /* MapViewModel.swift */; }; 1F4C836428300D25006971C0 /* MapViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4C836328300D25006971C0 /* MapViewModel+State.swift */; }; 1F4C836728300E79006971C0 /* CafeteriasService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4C836628300E79006971C0 /* CafeteriasService.swift */; }; 1F4C926F2882FD85003DC7D7 /* RoundedCorners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4C926E2882FD84003DC7D7 /* RoundedCorners.swift */; }; + 1F504172295B82F900A62CA1 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F504171295B82F900A62CA1 /* SearchView.swift */; }; + 1F504176295B83AA00A62CA1 /* SearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F504175295B83AA00A62CA1 /* SearchResultView.swift */; }; + 1F504178295B83FC00A62CA1 /* SearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F504177295B83FC00A62CA1 /* SearchResultViewModel.swift */; }; + 1F50417B295B844A00A62CA1 /* CafeteriaSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F50417A295B844A00A62CA1 /* CafeteriaSearchResultView.swift */; }; + 1F50417E295B848000A62CA1 /* CafeteriaSearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F50417D295B848000A62CA1 /* CafeteriaSearchResultViewModel.swift */; }; + 1F504180295B84A300A62CA1 /* GradeSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F50417F295B84A300A62CA1 /* GradeSearchResultView.swift */; }; + 1F504185295B84DC00A62CA1 /* ComparisonToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F504184295B84DC00A62CA1 /* ComparisonToken.swift */; }; + 1F504187295B84F400A62CA1 /* Searchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F504186295B84F400A62CA1 /* Searchable.swift */; }; + 1F50418A295B852F00A62CA1 /* GradeSearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F504189295B852F00A62CA1 /* GradeSearchResultViewModel.swift */; }; + 1F50418C295B854900A62CA1 /* GlobalSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F50418B295B854900A62CA1 /* GlobalSearch.swift */; }; + 1F50418E295B857B00A62CA1 /* String+Levenshtein.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F50418D295B857B00A62CA1 /* String+Levenshtein.swift */; }; + 1F504190295B85AB00A62CA1 /* String+Keep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F50418F295B85AB00A62CA1 /* String+Keep.swift */; }; + 1F504193295B897600A62CA1 /* StudyRoomSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F504192295B897600A62CA1 /* StudyRoomSearchResultView.swift */; }; + 1F504195295B898000A62CA1 /* StudyRoomSearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F504194295B898000A62CA1 /* StudyRoomSearchResultViewModel.swift */; }; + 1F504199295CC24E00A62CA1 /* EventSearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F504198295CC24E00A62CA1 /* EventSearchResultViewModel.swift */; }; + 1F50419B295CC25E00A62CA1 /* EventSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F50419A295CC25E00A62CA1 /* EventSearchResultView.swift */; }; + 1F50419D295D861D00A62CA1 /* CalendarService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F50419C295D861D00A62CA1 /* CalendarService.swift */; }; 1F54244F285CA059008363BC /* token-tutorial.mov in Resources */ = {isa = PBXBuildFile; fileRef = 1F54244E285CA059008363BC /* token-tutorial.mov */; }; 1F542450285CA059008363BC /* token-tutorial.mov in Resources */ = {isa = PBXBuildFile; fileRef = 1F54244E285CA059008363BC /* token-tutorial.mov */; }; 1F542451285CA059008363BC /* token-tutorial.mov in Resources */ = {isa = PBXBuildFile; fileRef = 1F54244E285CA059008363BC /* token-tutorial.mov */; }; - 1F69CE35297DB732005032CE /* NewsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F69CE34297DB732005032CE /* NewsService.swift */; }; - 1F69CE37297DCA22005032CE /* NewsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F69CE36297DCA22005032CE /* NewsScreen.swift */; }; - 1F69CE3C297DCC12005032CE /* MoviesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F69CE3B297DCC12005032CE /* MoviesScreen.swift */; }; - 1F69CE3E297DCC19005032CE /* MovieService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F69CE3D297DCC19005032CE /* MovieService.swift */; }; - 1F69CE40297DDCD3005032CE /* StudyRoomDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F69CE3F297DDCD3005032CE /* StudyRoomDetailsScreen.swift */; }; - 1F69CE42297EC94E005032CE /* DishService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F69CE41297EC94E005032CE /* DishService.swift */; }; - 1F69CE44297EC97E005032CE /* DishViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F69CE43297EC97E005032CE /* DishViewModel.swift */; }; - 1F69CE46297EC99D005032CE /* DishView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F69CE45297EC99D005032CE /* DishView.swift */; }; - 1F69CE48297EDEA3005032CE /* TUMSexyScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F69CE47297EDEA3005032CE /* TUMSexyScreen.swift */; }; - 1F69CE4B297EDEC7005032CE /* TUMSexyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F69CE4A297EDEC7005032CE /* TUMSexyService.swift */; }; - 1F69CE4E297EDF12005032CE /* TUMSexyLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F69CE4D297EDF12005032CE /* TUMSexyLink.swift */; }; - 1F71E80329E4611000379428 /* NavigaTumRoomFinderMaps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E7F629E4611000379428 /* NavigaTumRoomFinderMaps.swift */; }; - 1F71E80429E4611000379428 /* NavigaTumNavigationCoordinates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E7F729E4611000379428 /* NavigaTumNavigationCoordinates.swift */; }; - 1F71E80529E4611000379428 /* NavigaTumOverlaysMaps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E7F829E4611000379428 /* NavigaTumOverlaysMaps.swift */; }; - 1F71E80629E4611000379428 /* NavigaTumNavigationMaps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E7F929E4611000379428 /* NavigaTumNavigationMaps.swift */; }; - 1F71E80729E4611000379428 /* NavigaTumNavigationAdditionalProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E7FA29E4611000379428 /* NavigaTumNavigationAdditionalProperties.swift */; }; - 1F71E80829E4611000379428 /* NavigaTumOverlayMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E7FB29E4611000379428 /* NavigaTumOverlayMap.swift */; }; - 1F71E80929E4611000379428 /* NavigaTumNavigationDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E7FC29E4611000379428 /* NavigaTumNavigationDetails.swift */; }; - 1F71E80A29E4611000379428 /* NavigaTumSearchResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E7FE29E4611000379428 /* NavigaTumSearchResponse.swift */; }; - 1F71E80B29E4611000379428 /* NavigaTumSearchResponseSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E7FF29E4611000379428 /* NavigaTumSearchResponseSection.swift */; }; - 1F71E80C29E4611000379428 /* NavigaTumNavigationEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E80029E4611000379428 /* NavigaTumNavigationEntity.swift */; }; - 1F71E80D29E4611000379428 /* NavigaTumNavigationProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E80129E4611000379428 /* NavigaTumNavigationProperty.swift */; }; - 1F71E80E29E4611000379428 /* NavigaTumRoomFinderMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E80229E4611000379428 /* NavigaTumRoomFinderMap.swift */; }; - 1F71E81629E4611E00379428 /* RoomFinderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E81529E4611E00379428 /* RoomFinderService.swift */; }; - 1F71E81929E4613500379428 /* NavigaTumDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E81729E4613500379428 /* NavigaTumDetailsViewModel.swift */; }; - 1F71E81A29E4613500379428 /* NavigaTumViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E81829E4613500379428 /* NavigaTumViewModel.swift */; }; - 1F71E82229E4613F00379428 /* NavigaTumDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E81C29E4613F00379428 /* NavigaTumDetailsView.swift */; }; - 1F71E82329E4613F00379428 /* NavigaTumDetailsBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E81D29E4613F00379428 /* NavigaTumDetailsBaseView.swift */; }; - 1F71E82429E4613F00379428 /* NavigaTumMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E81E29E4613F00379428 /* NavigaTumMapView.swift */; }; - 1F71E82529E4613F00379428 /* NavigaTumMapImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E81F29E4613F00379428 /* NavigaTumMapImagesView.swift */; }; - 1F71E82629E4613F00379428 /* NavigaTumView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E82029E4613F00379428 /* NavigaTumView.swift */; }; - 1F71E82729E4613F00379428 /* NavigaTumListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E82129E4613F00379428 /* NavigaTumListView.swift */; }; - 1F71E82D29E464C400379428 /* CafeteriaWidgetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E82C29E464C400379428 /* CafeteriaWidgetViewModel.swift */; }; - 1F71E82F29E4667C00379428 /* NavigaTUMAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E82E29E4667C00379428 /* NavigaTUMAPI.swift */; }; - 1F71E83129E46A1000379428 /* NavigaTUMAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E83029E46A1000379428 /* NavigaTUMAPIError.swift */; }; - 1FA538EE297560CD004C70A8 /* MainAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA538ED297560CD004C70A8 /* MainAPI.swift */; }; - 1FACF3F92996A49300A0B8AC /* TUMDevAppAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FACF3F82996A49300A0B8AC /* TUMDevAppAPIOld.swift */; }; - 1FACF3FB2996A65700A0B8AC /* MealPlanService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FACF3FA2996A65700A0B8AC /* MealPlanService.swift */; }; - 1FACF3FD2996E34200A0B8AC /* MealPlanScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FACF3FC2996E34200A0B8AC /* MealPlanScreen.swift */; }; + 1F63A3D229DDE68E00844981 /* TUMSexyLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3CD29DDE68D00844981 /* TUMSexyLink.swift */; }; + 1F63A3D329DDE68E00844981 /* TUMSexyScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3CF29DDE68E00844981 /* TUMSexyScreen.swift */; }; + 1F63A3D429DDE68E00844981 /* TUMSexyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3D129DDE68E00844981 /* TUMSexyService.swift */; }; + 1F63A3DA29DDE6D500844981 /* CalendarScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3D629DDE6D500844981 /* CalendarScreen.swift */; }; + 1F63A3DB29DDE6D500844981 /* CalendarEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3D829DDE6D500844981 /* CalendarEvent.swift */; }; + 1F63A3DC29DDE6D500844981 /* TumCalendarStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3D929DDE6D500844981 /* TumCalendarStyle.swift */; }; + 1F63A3E129DDE91100844981 /* Confirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3DE29DDE91100844981 /* Confirmation.swift */; }; + 1F63A3E229DDE91100844981 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3DF29DDE91100844981 /* Token.swift */; }; + 1F63A3E329DDE91100844981 /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3E029DDE91100844981 /* Credentials.swift */; }; + 1F63A3E629DDF50600844981 /* PersonSearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3E529DDF50600844981 /* PersonSearchService.swift */; }; + 1F63A3E929DDF50C00844981 /* PersonSearchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3E829DDF50C00844981 /* PersonSearchScreen.swift */; }; + 1F63A3EC29DDF97E00844981 /* PersonDetailedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3EB29DDF97E00844981 /* PersonDetailedScreen.swift */; }; + 1F63A3EF29DDF99600844981 /* LectureSearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3EE29DDF99600844981 /* LectureSearchService.swift */; }; + 1F63A3F229DDF99B00844981 /* LectureSearchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3F129DDF99B00844981 /* LectureSearchScreen.swift */; }; + 1F63A3F529DDFAA100844981 /* PersonDetailedService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3F429DDFAA100844981 /* PersonDetailedService.swift */; }; + 1F63A3F729DEC67E00844981 /* TuitionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3F629DEC67E00844981 /* TuitionScreen.swift */; }; + 1F63A3FA29DEC6A400844981 /* NewsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3F929DEC6A400844981 /* NewsScreen.swift */; }; + 1F63A3FD29DEC6E300844981 /* MovieScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A3FC29DEC6E300844981 /* MovieScreen.swift */; }; + 1F63A40129DECE0000844981 /* RoomFinderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A40029DECE0000844981 /* RoomFinderService.swift */; }; + 1F63A40329DECFA100844981 /* DishView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F63A40229DECFA100844981 /* DishView.swift */; }; + 1F6BD38F296F53E200CDD625 /* DataTypeClassifierV4English.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = 1F6BD38E296F53E200CDD625 /* DataTypeClassifierV4English.mlmodel */; }; + 1F6BD391296F53E900CDD625 /* DataTypeClassifierV4German.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = 1F6BD390296F53E900CDD625 /* DataTypeClassifierV4German.mlmodel */; }; + 1F71E84129E4800A00379428 /* NavigaTumRoomFinderMaps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E83429E4800A00379428 /* NavigaTumRoomFinderMaps.swift */; }; + 1F71E84229E4800A00379428 /* NavigaTumNavigationCoordinates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E83529E4800A00379428 /* NavigaTumNavigationCoordinates.swift */; }; + 1F71E84329E4800A00379428 /* NavigaTumOverlaysMaps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E83629E4800A00379428 /* NavigaTumOverlaysMaps.swift */; }; + 1F71E84429E4800A00379428 /* NavigaTumNavigationMaps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E83729E4800A00379428 /* NavigaTumNavigationMaps.swift */; }; + 1F71E84529E4800A00379428 /* NavigaTumNavigationAdditionalProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E83829E4800A00379428 /* NavigaTumNavigationAdditionalProperties.swift */; }; + 1F71E84629E4800A00379428 /* NavigaTumOverlayMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E83929E4800A00379428 /* NavigaTumOverlayMap.swift */; }; + 1F71E84729E4800A00379428 /* NavigaTumNavigationDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E83A29E4800A00379428 /* NavigaTumNavigationDetails.swift */; }; + 1F71E84829E4800A00379428 /* NavigaTumSearchResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E83C29E4800A00379428 /* NavigaTumSearchResponse.swift */; }; + 1F71E84929E4800A00379428 /* NavigaTumSearchResponseSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E83D29E4800A00379428 /* NavigaTumSearchResponseSection.swift */; }; + 1F71E84A29E4800A00379428 /* NavigaTumNavigationEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E83E29E4800A00379428 /* NavigaTumNavigationEntity.swift */; }; + 1F71E84B29E4800A00379428 /* NavigaTumNavigationProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E83F29E4800A00379428 /* NavigaTumNavigationProperty.swift */; }; + 1F71E84C29E4800A00379428 /* NavigaTumRoomFinderMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E84029E4800A00379428 /* NavigaTumRoomFinderMap.swift */; }; + 1F71E84F29E4802800379428 /* NavigaTumDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E84D29E4802800379428 /* NavigaTumDetailsViewModel.swift */; }; + 1F71E85029E4802800379428 /* NavigaTumViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E84E29E4802800379428 /* NavigaTumViewModel.swift */; }; + 1F71E85829E4803400379428 /* NavigaTumDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E85229E4803400379428 /* NavigaTumDetailsView.swift */; }; + 1F71E85929E4803400379428 /* NavigaTumDetailsBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E85329E4803400379428 /* NavigaTumDetailsBaseView.swift */; }; + 1F71E85A29E4803400379428 /* NavigaTumMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E85429E4803400379428 /* NavigaTumMapView.swift */; }; + 1F71E85B29E4803400379428 /* NavigaTumMapImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E85529E4803400379428 /* NavigaTumMapImagesView.swift */; }; + 1F71E85C29E4803400379428 /* NavigaTumView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E85629E4803400379428 /* NavigaTumView.swift */; }; + 1F71E85D29E4803400379428 /* NavigaTumListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E85729E4803400379428 /* NavigaTumListView.swift */; }; + 1F71E85F29E4833500379428 /* NavigaTUMAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E85E29E4833500379428 /* NavigaTUMAPI.swift */; }; + 1F71E86129E4834200379428 /* NavigaTUMAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71E86029E4834200379428 /* NavigaTUMAPIError.swift */; }; + 1F74DB2D2971D0D200B9143C /* NewsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F74DB2C2971D0D200B9143C /* NewsService.swift */; }; + 1F74DB2F2971E01100B9143C /* RoomFinderSearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F74DB2E2971E01100B9143C /* RoomFinderSearchResultViewModel.swift */; }; + 1F74DB312971E01F00B9143C /* RoomFinderSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F74DB302971E01F00B9143C /* RoomFinderSearchResultView.swift */; }; + 1F74DB332972B1C600B9143C /* LectureSearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F74DB322972B1C600B9143C /* LectureSearchResultViewModel.swift */; }; + 1F74DB352972B22F00B9143C /* LectureSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F74DB342972B22F00B9143C /* LectureSearchResultView.swift */; }; + 1F74DB372972B87800B9143C /* PersonSearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F74DB362972B87800B9143C /* PersonSearchResultViewModel.swift */; }; + 1F74DB392972B92D00B9143C /* PersonSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F74DB382972B92D00B9143C /* PersonSearchResultView.swift */; }; + 1F74DB3B2973038800B9143C /* MovieService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F74DB3A2973038800B9143C /* MovieService.swift */; }; + 1FAC1A6F2A0632F900A0A4F3 /* View+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAC1A6E2A0632F900A0A4F3 /* View+Search.swift */; }; + 1FAC1A722A0644B600A0A4F3 /* ExpandIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAC1A712A0644B600A0A4F3 /* ExpandIcon.swift */; }; 1FAF9F0C284D2ABC000ABE93 /* MapScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAF9F0B284D2ABC000ABE93 /* MapScreenView.swift */; }; 1FB82E3428F95776007B1858 /* TokenPermissionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB82E3328F95776007B1858 /* TokenPermissionsView.swift */; }; 1FB82E3628F96C9E007B1858 /* TokenPermissionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB82E3528F96C9E007B1858 /* TokenPermissionsViewModel.swift */; }; 1FBFA168285E5B2D00FC1515 /* PanelContentListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FBFA167285E5B2D00FC1515 /* PanelContentListView.swift */; }; - 1FFF9AC6297D31830098E874 /* ProfileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FFF9AC5297D31830098E874 /* ProfileService.swift */; }; + 1FD025D22971A31300E66981 /* NewsSearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD025D12971A31300E66981 /* NewsSearchResultViewModel.swift */; }; + 1FD025D72971BD2000E66981 /* NewsSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD025D62971BD2000E66981 /* NewsSearchResultView.swift */; }; + 1FE6DC9F29B74ACA006A06B9 /* GradeSearchResultScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DC9E29B74ACA006A06B9 /* GradeSearchResultScreen.swift */; }; + 1FE6DCA229B77012006A06B9 /* SearchResultErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCA129B77012006A06B9 /* SearchResultErrorView.swift */; }; + 1FE6DCA429B77094006A06B9 /* SearchResultLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCA329B77094006A06B9 /* SearchResultLoadingView.swift */; }; + 1FE6DCA729B77978006A06B9 /* LectureSearchResultScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCA629B77978006A06B9 /* LectureSearchResultScreen.swift */; }; + 1FE6DCAA29B78131006A06B9 /* CafeteriaSearchResultScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCA929B78131006A06B9 /* CafeteriaSearchResultScreen.swift */; }; + 1FE6DCAC29B790F8006A06B9 /* SearchError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCAB29B790F8006A06B9 /* SearchError.swift */; }; + 1FE6DCAF29B79281006A06B9 /* StudyRoomSearchResultScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCAE29B79280006A06B9 /* StudyRoomSearchResultScreen.swift */; }; + 1FE6DCB229B79B6E006A06B9 /* EventSearchResultScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCB129B79B6E006A06B9 /* EventSearchResultScreen.swift */; }; + 1FE6DCB529B79FDF006A06B9 /* MovieSearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCB429B79FDF006A06B9 /* MovieSearchResultViewModel.swift */; }; + 1FE6DCB729B7A2B0006A06B9 /* MovieSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCB629B7A2B0006A06B9 /* MovieSearchResultView.swift */; }; + 1FE6DCBA29B7A7FE006A06B9 /* MovieSearchResultScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCB929B7A7FE006A06B9 /* MovieSearchResultScreen.swift */; }; + 1FE6DCBC29B7A824006A06B9 /* NewsSearchResultScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCBB29B7A824006A06B9 /* NewsSearchResultScreen.swift */; }; + 1FE6DCBF29B7AB76006A06B9 /* RoomFinderSearchResultScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCBE29B7AB76006A06B9 /* RoomFinderSearchResultScreen.swift */; }; + 1FE6DCC229B7ADB7006A06B9 /* PersonSearchResultScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FE6DCC129B7ADB7006A06B9 /* PersonSearchResultScreen.swift */; }; 226CB51E2798DF9C0043ABCA /* Snap in Frameworks */ = {isa = PBXBuildFile; productRef = 226CB51D2798DF9C0043ABCA /* Snap */; settings = {ATTRIBUTES = (Required, ); }; }; 2F1B2B8528652FC90023BD9A /* MovieDetailsBasicInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F1B2B8428652FC90023BD9A /* MovieDetailsBasicInfoView.swift */; }; 2F1B2B87286530120023BD9A /* MovieDetailsBasicInfoRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F1B2B86286530120023BD9A /* MovieDetailsBasicInfoRowView.swift */; }; @@ -156,17 +228,17 @@ 36108BE327A304B5007DC62D /* DishLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BCF27A304B5007DC62D /* DishLabel.swift */; }; 36108BE527A304B5007DC62D /* MealPlanView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BD227A304B5007DC62D /* MealPlanView.swift */; }; 36108BE627A304B5007DC62D /* MealPlanViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BD327A304B5007DC62D /* MealPlanViewModel.swift */; }; - 36108BE727A304B5007DC62D /* MenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BD427A304B5007DC62D /* MenuViewModel.swift */; }; + 36108BE727A304B5007DC62D /* Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BD427A304B5007DC62D /* Menu.swift */; }; 36108BE927A304B5007DC62D /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BD627A304B5007DC62D /* MenuView.swift */; }; 36108BEB27A304B6007DC62D /* CafeteriaRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BD927A304B5007DC62D /* CafeteriaRowView.swift */; }; 36108BEF27A304B6007DC62D /* MapContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BDD27A304B5007DC62D /* MapContentView.swift */; }; 36108BF027A304B6007DC62D /* PanelContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BDE27A304B5007DC62D /* PanelContentView.swift */; }; 36108BFA27A30517007DC62D /* Movie.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BF327A30516007DC62D /* Movie.swift */; }; - 36108BFB27A30517007DC62D /* MoviesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BF427A30516007DC62D /* MoviesViewModel.swift */; }; + 36108BFB27A30517007DC62D /* MovieViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BF427A30516007DC62D /* MovieViewModel.swift */; }; 36108BFC27A30517007DC62D /* MovieCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BF627A30516007DC62D /* MovieCard.swift */; }; 36108BFD27A30517007DC62D /* MovieDetailedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BF727A30516007DC62D /* MovieDetailedView.swift */; }; 36108BFE27A30517007DC62D /* MovieDetailCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BF827A30516007DC62D /* MovieDetailCellView.swift */; }; - 36108BFF27A30517007DC62D /* MoviesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BF927A30516007DC62D /* MoviesView.swift */; }; + 36108BFF27A30517007DC62D /* MovieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108BF927A30516007DC62D /* MovieView.swift */; }; 36108C0227A30762007DC62D /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108C0127A30762007DC62D /* Enums.swift */; }; 36108C1427A307F9007DC62D /* GradeColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108C0527A307F9007DC62D /* GradeColor.swift */; }; 36108C1527A307F9007DC62D /* GradesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36108C0627A307F9007DC62D /* GradesViewModel.swift */; }; @@ -186,7 +258,6 @@ 36203E832761BDD100C24658 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 36203E822761BDD100C24658 /* KeychainAccess */; }; 36203E8B2761C6EC00C24658 /* TUMSplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36203E882761C6EC00C24658 /* TUMSplashScreen.swift */; }; 36203E8C2761C6EC00C24658 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36203E892761C6EC00C24658 /* LoginView.swift */; }; - 36203E8D2761C6EC00C24658 /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36203E8A2761C6EC00C24658 /* Credentials.swift */; }; 3629BA2C27A1CECA0036AC80 /* NewsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3629BA2B27A1CECA0036AC80 /* NewsView.swift */; }; 3629BA2E27A1CEFA0036AC80 /* NewsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3629BA2D27A1CEFA0036AC80 /* NewsCard.swift */; }; 3629BA3127A1D0AD0036AC80 /* ScrollableCardsViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3629BA3027A1D0AD0036AC80 /* ScrollableCardsViewModifier.swift */; }; @@ -228,22 +299,10 @@ 36982BD827A2739000515847 /* Collapsible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36982BD727A2739000515847 /* Collapsible.swift */; }; 3698CBED2761E014001C5735 /* CustomRoundedBorderTextFieldStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3698CBEC2761E014001C5735 /* CustomRoundedBorderTextFieldStyle.swift */; }; 3698CBEF2761E6CC001C5735 /* TokenConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3698CBEE2761E6CC001C5735 /* TokenConfirmationView.swift */; }; - 36AD5CF227B7FEAD00DAE143 /* TumCalendarStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AD5CF127B7FEAD00DAE143 /* TumCalendarStyle.swift */; }; 36AD5CF427B8C83500DAE143 /* CalendarSingleEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AD5CF327B8C83500DAE143 /* CalendarSingleEventView.swift */; }; 36AD5CF627B8D97500DAE143 /* LectureDetailsEventInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AD5CF527B8D97500DAE143 /* LectureDetailsEventInfoView.swift */; }; - 36AD5CF827B96AD200DAE143 /* PersonSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AD5CF727B96AD200DAE143 /* PersonSearchView.swift */; }; - 36AD5CFA27B9711B00DAE143 /* LectureSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AD5CF927B9711B00DAE143 /* LectureSearchView.swift */; }; - 36AD5CFC27B974F100DAE143 /* TuitionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AD5CFB27B974F100DAE143 /* TuitionScreen.swift */; }; 36AD5CFE27BA064E00DAE143 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 36AD5CFD27BA064E00DAE143 /* GoogleService-Info.plist */; }; - 36AF61D927A2FD7800FEBD98 /* TUMSexyAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AF61BB27A2FD7700FEBD98 /* TUMSexyAPIOld.swift */; }; - 36AF61DA27A2FD7800FEBD98 /* EatAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AF61BC27A2FD7700FEBD98 /* EatAPIOld.swift */; }; - 36AF61DB27A2FD7800FEBD98 /* MVGAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AF61BD27A2FD7700FEBD98 /* MVGAPIOld.swift */; }; - 36AF61DC27A2FD7800FEBD98 /* NetworkingAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AF61BE27A2FD7700FEBD98 /* NetworkingAPIOld.swift */; }; - 36AF61DD27A2FD7800FEBD98 /* CampusOnlineAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AF61BF27A2FD7700FEBD98 /* CampusOnlineAPIOld.swift */; }; - 36AF61DE27A2FD7800FEBD98 /* APIResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AF61C027A2FD7700FEBD98 /* APIResponse.swift */; }; - 36AF61DF27A2FD7800FEBD98 /* TUMOnlineAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AF61C127A2FD7700FEBD98 /* TUMOnlineAPIOld.swift */; }; 36AF61E027A2FD7800FEBD98 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AF61C227A2FD7700FEBD98 /* Cache.swift */; }; - 36AF61E127A2FD7800FEBD98 /* TUMCabeAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AF61C327A2FD7700FEBD98 /* TUMCabeAPIOld.swift */; }; 36AF61E227A2FD7800FEBD98 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AF61C527A2FD7700FEBD98 /* Constants.swift */; }; 36AF61E327A2FD7800FEBD98 /* APIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AF61C627A2FD7700FEBD98 /* APIConstants.swift */; }; 36AF61E427A2FD7800FEBD98 /* NetworkingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AF61C827A2FD7700FEBD98 /* NetworkingError.swift */; }; @@ -263,7 +322,7 @@ 36BB6F5327AFCCB500F224AB /* PersonDetailedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F5227AFCCB500F224AB /* PersonDetailedView.swift */; }; 36BB6F6027AFCDFA00F224AB /* PersonSearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F5B27AFCDF900F224AB /* PersonSearchViewModel.swift */; }; 36BB6F6127AFCDFA00F224AB /* Person.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F5D27AFCDFA00F224AB /* Person.swift */; }; - 36BB6F6227AFCDFA00F224AB /* PersonSearchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F5F27AFCDFA00F224AB /* PersonSearchScreen.swift */; }; + 36BB6F6227AFCDFA00F224AB /* PersonSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F5F27AFCDFA00F224AB /* PersonSearchView.swift */; }; 36BB6F6427AFCFFB00F224AB /* PersonDetailedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F6327AFCFFB00F224AB /* PersonDetailedViewModel.swift */; }; 36BB6F6627AFD12B00F224AB /* PersonDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F6527AFD12B00F224AB /* PersonDetails.swift */; }; 36BB6F6827AFD26500F224AB /* Organization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F6727AFD26500F224AB /* Organization.swift */; }; @@ -275,7 +334,7 @@ 36BB6F7927B26DE300F224AB /* TuitionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F7827B26DE300F224AB /* TuitionView.swift */; }; 36BB6F7B27B27D0D00F224AB /* TuitionCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F7A27B27D0D00F224AB /* TuitionCard.swift */; }; 36BB6F7F27B386D100F224AB /* AddToContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F7E27B386D100F224AB /* AddToContactsView.swift */; }; - 36BB6F8327B39B4300F224AB /* LectureSearchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F8227B39B4300F224AB /* LectureSearchScreen.swift */; }; + 36BB6F8327B39B4300F224AB /* LectureSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F8227B39B4300F224AB /* LectureSearchView.swift */; }; 36BB6F8627B39C5300F224AB /* LectureSearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F8527B39C5300F224AB /* LectureSearchViewModel.swift */; }; 36BB6F8D27B3F25A00F224AB /* NSMutableString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BB6F8C27B3F25A00F224AB /* NSMutableString+Extensions.swift */; }; 36BBE72F27989F8C0018FD3F /* SFSafariViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BBE72E27989F8C0018FD3F /* SFSafariViewWrapper.swift */; }; @@ -312,11 +371,6 @@ 97997AE7277234120079F809 /* XMLCoder in Frameworks */ = {isa = PBXBuildFile; productRef = 97997AE6277234120079F809 /* XMLCoder */; }; 97C9AB1227732A200097B10C /* SwiftUICharts in Frameworks */ = {isa = PBXBuildFile; productRef = 97C9AB1127732A200097B10C /* SwiftUICharts */; }; 97F8A79327E641570099EE83 /* AcademicDegree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F8A79227E641570099EE83 /* AcademicDegree.swift */; }; - 9926FD432A1D0BC000EE3ECB /* GradesStudyProgramView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9926FD422A1D0BC000EE3ECB /* GradesStudyProgramView.swift */; }; - 9926FD452A1D0DBC00EE3ECB /* GradesSemesterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9926FD442A1D0DBC00EE3ECB /* GradesSemesterView.swift */; }; - 99706870298569E10028D235 /* CrashlyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9970686F298569E10028D235 /* CrashlyticsService.swift */; }; - 99EE85342A0920AE00973916 /* AverageGradesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99EE85332A0920AE00973916 /* AverageGradesService.swift */; }; - 99EE85362A09216600973916 /* AverageGrade.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99EE85352A09216600973916 /* AverageGrade.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -362,6 +416,7 @@ 08DFB97428664CFC00E357DF /* TuitionDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuitionDetailsView.swift; sourceTree = ""; }; 08DFB9762866506900E357DF /* WidgetFrameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetFrameView.swift; sourceTree = ""; }; 08DFB97828666AD900E357DF /* CafeteriaWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CafeteriaWidgetView.swift; sourceTree = ""; }; + 08DFB97C2867800C00E357DF /* CafeteriaWidgetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CafeteriaWidgetViewModel.swift; sourceTree = ""; }; 08DFB97E2867AC9200E357DF /* StudyRoomWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyRoomWidgetView.swift; sourceTree = ""; }; 08DFB9802867ACB600E357DF /* StudyRoomWidgetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyRoomWidgetViewModel.swift; sourceTree = ""; }; 08FAFD14287DC484006A0E27 /* CalendarWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarWidgetView.swift; sourceTree = ""; }; @@ -375,84 +430,155 @@ 08FAFD282898B6C8006A0E27 /* SpatioTemporalStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpatioTemporalStrategy.swift; sourceTree = ""; }; 100803452764E2C50013ED0E /* ProfileToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileToolbar.swift; sourceTree = ""; }; 100803472764E37A0013ED0E /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; - 1F04F16D297A9A700085F273 /* CalendarService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarService.swift; sourceTree = ""; }; - 1F04F170297AA5F30085F273 /* Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; }; - 1F04F172297AD41B0085F273 /* CalendarEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarEvent.swift; sourceTree = ""; }; - 1F04F174297AD4280085F273 /* CalendarScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarScreen.swift; sourceTree = ""; }; - 1F04F178297AED150085F273 /* LectureSearchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LectureSearchService.swift; sourceTree = ""; }; - 1F04F17E297BDF1E0085F273 /* PersonSearchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonSearchService.swift; sourceTree = ""; }; - 1F04F182297C3EF70085F273 /* PersonDetailedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonDetailedScreen.swift; sourceTree = ""; }; - 1F04F184297C3F990085F273 /* PersonDetailedService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonDetailedService.swift; sourceTree = ""; }; - 1F04F189297C85120085F273 /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; - 1F04F18B297C85190085F273 /* Confirmation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Confirmation.swift; sourceTree = ""; }; - 1F183A162979D19000B5D22D /* APIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; - 1F189E8C29968CE50056BBD8 /* TUMOnlineAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TUMOnlineAPI.swift; sourceTree = ""; }; - 1F189E8E29968CFC0056BBD8 /* TUMCabeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TUMCabeAPI.swift; sourceTree = ""; }; - 1F189E9029968D130056BBD8 /* EatAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EatAPI.swift; sourceTree = ""; }; - 1F189E9229968D260056BBD8 /* TUMDevAppAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TUMDevAppAPI.swift; sourceTree = ""; }; - 1F189E9429968D330056BBD8 /* TUMSexyAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TUMSexyAPI.swift; sourceTree = ""; }; - 1F189E9629968D490056BBD8 /* MVGAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVGAPI.swift; sourceTree = ""; }; - 1F189E9929968D790056BBD8 /* TUMOnlineAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TUMOnlineAPIError.swift; sourceTree = ""; }; - 1F189E9B29968D880056BBD8 /* TUMCabeAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TUMCabeAPIError.swift; sourceTree = ""; }; - 1F189E9D29968D9B0056BBD8 /* EatAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EatAPIError.swift; sourceTree = ""; }; - 1F189E9F29968DA90056BBD8 /* TUMDevAppAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TUMDevAppAPIError.swift; sourceTree = ""; }; - 1F189EA129968DB90056BBD8 /* TUMSexyAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TUMSexyAPIError.swift; sourceTree = ""; }; - 1F189EA329968DE60056BBD8 /* MVGAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVGAPIError.swift; sourceTree = ""; }; - 1F189EA629968E5C0056BBD8 /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; + 1ED0F9D42A6534D400A27A75 /* GradesStudyProgramView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradesStudyProgramView.swift; sourceTree = ""; }; + 1ED0F9D62A6534DE00A27A75 /* GradesSemesterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradesSemesterView.swift; sourceTree = ""; }; + 1ED0F9D82A65351C00A27A75 /* AverageGradesService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AverageGradesService.swift; sourceTree = ""; }; + 1ED0F9DA2A65353000A27A75 /* AverageGrade.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AverageGrade.swift; sourceTree = ""; }; + 1F0E8EBA293E22FF006E8BB9 /* DataTypeClassifierV2.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = DataTypeClassifierV2.mlmodel; sourceTree = ""; }; + 1F0F39692A058AAA00E55FCB /* Grade+PreviewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Grade+PreviewData.swift"; sourceTree = ""; }; + 1F0F396B2A058AEA00E55FCB /* News+PreviewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "News+PreviewData.swift"; sourceTree = ""; }; + 1F0F396E2A058B5000E55FCB /* Movie+PreviewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Movie+PreviewData.swift"; sourceTree = ""; }; + 1F0F39702A058B9F00E55FCB /* StudyRoomApiResponse+PreviewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StudyRoomApiResponse+PreviewData.swift"; sourceTree = ""; }; + 1F0F39722A05913F00E55FCB /* Cafeteria+PreviewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Cafeteria+PreviewData.swift"; sourceTree = ""; }; + 1F0F39742A05917400E55FCB /* CalendarEvent+PreviewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CalendarEvent+PreviewData.swift"; sourceTree = ""; }; + 1F0F39762A05975200E55FCB /* SearchState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchState.swift; sourceTree = ""; }; + 1F1F42B22999331F00A0D0B7 /* SearchResultBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultBarView.swift; sourceTree = ""; }; 1F2068DB28FD6E2800DBDF67 /* LoginViewModel+LoginState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginViewModel+LoginState.swift"; sourceTree = ""; }; 1F2068DD28FD731200DBDF67 /* LoginViewModel+TokenState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginViewModel+TokenState.swift"; sourceTree = ""; }; 1F2068E128FD73C400DBDF67 /* TokenPermissionsViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TokenPermissionsViewModel+State.swift"; sourceTree = ""; }; 1F2068E328FD73CB00DBDF67 /* TokenPermissionsViewModel+PermissionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TokenPermissionsViewModel+PermissionType.swift"; sourceTree = ""; }; + 1F26155E29D9E8BD0062B8E5 /* APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; + 1F26155F29D9E8BD0062B8E5 /* Service.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; }; + 1F26156029D9E8BD0062B8E5 /* MainAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainAPI.swift; sourceTree = ""; }; + 1F26156129D9E8BD0062B8E5 /* API.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; + 1F26156729D9E8DC0062B8E5 /* TUMDevAppAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMDevAppAPIOld.swift; sourceTree = ""; }; + 1F26156829D9E8DC0062B8E5 /* EatAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EatAPIOld.swift; sourceTree = ""; }; + 1F26156929D9E8DC0062B8E5 /* MVGAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MVGAPIOld.swift; sourceTree = ""; }; + 1F26156A29D9E8DC0062B8E5 /* CampusOnlineAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CampusOnlineAPIOld.swift; sourceTree = ""; }; + 1F26156B29D9E8DC0062B8E5 /* TUMCabeAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMCabeAPIOld.swift; sourceTree = ""; }; + 1F26156C29D9E8DC0062B8E5 /* TUMSexyAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMSexyAPIOld.swift; sourceTree = ""; }; + 1F26156D29D9E8DC0062B8E5 /* TUMOnlineAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMOnlineAPIOld.swift; sourceTree = ""; }; + 1F26156E29D9E8DC0062B8E5 /* APIResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIResponse.swift; sourceTree = ""; }; + 1F26156F29D9E8DC0062B8E5 /* NetworkingAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkingAPIOld.swift; sourceTree = ""; }; + 1F26157129D9E8DC0062B8E5 /* TUMDevAppAPIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMDevAppAPIError.swift; sourceTree = ""; }; + 1F26157229D9E8DC0062B8E5 /* TUMOnlineAPIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMOnlineAPIError.swift; sourceTree = ""; }; + 1F26157329D9E8DC0062B8E5 /* EatAPIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EatAPIError.swift; sourceTree = ""; }; + 1F26157429D9E8DC0062B8E5 /* TUMCabeAPIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMCabeAPIError.swift; sourceTree = ""; }; + 1F26157529D9E8DC0062B8E5 /* MVGAPIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MVGAPIError.swift; sourceTree = ""; }; + 1F26157629D9E8DC0062B8E5 /* TUMSexyAPIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMSexyAPIError.swift; sourceTree = ""; }; + 1F26157829D9E8DC0062B8E5 /* TUMSexyAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMSexyAPI.swift; sourceTree = ""; }; + 1F26157929D9E8DC0062B8E5 /* EatAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EatAPI.swift; sourceTree = ""; }; + 1F26157A29D9E8DC0062B8E5 /* TUMDevAppAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMDevAppAPI.swift; sourceTree = ""; }; + 1F26157B29D9E8DC0062B8E5 /* MVGAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MVGAPI.swift; sourceTree = ""; }; + 1F26157C29D9E8DC0062B8E5 /* TUMOnlineAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMOnlineAPI.swift; sourceTree = ""; }; + 1F26157D29D9E8DC0062B8E5 /* TUMCabeAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMCabeAPI.swift; sourceTree = ""; }; + 1F26159429D9E8F10062B8E5 /* ProfileService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileService.swift; sourceTree = ""; }; + 1F26159729D9E9010062B8E5 /* CrashlyticsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashlyticsService.swift; sourceTree = ""; }; + 1F26159929DA00EB0062B8E5 /* MensaCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MensaCategory.swift; sourceTree = ""; }; + 1F26159B29DA015D0062B8E5 /* DishService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DishService.swift; sourceTree = ""; }; + 1F26159C29DA015D0062B8E5 /* MealPlanService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealPlanService.swift; sourceTree = ""; }; + 1F26159F29DA021D0062B8E5 /* StudyRoomDetailsScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StudyRoomDetailsScreen.swift; sourceTree = ""; }; + 1F2615A129DA02E10062B8E5 /* MealPlanScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealPlanScreen.swift; sourceTree = ""; }; + 1F2615A329DA02FC0062B8E5 /* DishViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DishViewModel.swift; sourceTree = ""; }; 1F33B2EC282B084100C898E4 /* MockGradesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGradesViewModel.swift; sourceTree = ""; }; + 1F36EC8129377CD300349FC1 /* DataTypeClassifierV1.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = DataTypeClassifierV1.mlmodel; sourceTree = ""; }; + 1F488335296EF0CD0002CE77 /* DataTypeClassifierV3.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = DataTypeClassifierV3.mlmodel; sourceTree = ""; }; 1F4C836128300306006971C0 /* MapViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewModel.swift; sourceTree = ""; }; 1F4C836328300D25006971C0 /* MapViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MapViewModel+State.swift"; sourceTree = ""; }; 1F4C836628300E79006971C0 /* CafeteriasService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CafeteriasService.swift; sourceTree = ""; }; 1F4C926E2882FD84003DC7D7 /* RoundedCorners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCorners.swift; sourceTree = ""; }; + 1F504171295B82F900A62CA1 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; + 1F504175295B83AA00A62CA1 /* SearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultView.swift; sourceTree = ""; }; + 1F504177295B83FC00A62CA1 /* SearchResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultViewModel.swift; sourceTree = ""; }; + 1F50417A295B844A00A62CA1 /* CafeteriaSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CafeteriaSearchResultView.swift; sourceTree = ""; }; + 1F50417D295B848000A62CA1 /* CafeteriaSearchResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CafeteriaSearchResultViewModel.swift; sourceTree = ""; }; + 1F50417F295B84A300A62CA1 /* GradeSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradeSearchResultView.swift; sourceTree = ""; }; + 1F504184295B84DC00A62CA1 /* ComparisonToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComparisonToken.swift; sourceTree = ""; }; + 1F504186295B84F400A62CA1 /* Searchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Searchable.swift; sourceTree = ""; }; + 1F504189295B852F00A62CA1 /* GradeSearchResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradeSearchResultViewModel.swift; sourceTree = ""; }; + 1F50418B295B854900A62CA1 /* GlobalSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearch.swift; sourceTree = ""; }; + 1F50418D295B857B00A62CA1 /* String+Levenshtein.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Levenshtein.swift"; sourceTree = ""; }; + 1F50418F295B85AB00A62CA1 /* String+Keep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Keep.swift"; sourceTree = ""; }; + 1F504192295B897600A62CA1 /* StudyRoomSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyRoomSearchResultView.swift; sourceTree = ""; }; + 1F504194295B898000A62CA1 /* StudyRoomSearchResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyRoomSearchResultViewModel.swift; sourceTree = ""; }; + 1F504198295CC24E00A62CA1 /* EventSearchResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventSearchResultViewModel.swift; sourceTree = ""; }; + 1F50419A295CC25E00A62CA1 /* EventSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventSearchResultView.swift; sourceTree = ""; }; + 1F50419C295D861D00A62CA1 /* CalendarService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarService.swift; sourceTree = ""; }; 1F54244E285CA059008363BC /* token-tutorial.mov */ = {isa = PBXFileReference; lastKnownFileType = video.quicktime; path = "token-tutorial.mov"; sourceTree = ""; }; - 1F69CE34297DB732005032CE /* NewsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsService.swift; sourceTree = ""; }; - 1F69CE36297DCA22005032CE /* NewsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsScreen.swift; sourceTree = ""; }; - 1F69CE3B297DCC12005032CE /* MoviesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoviesScreen.swift; sourceTree = ""; }; - 1F69CE3D297DCC19005032CE /* MovieService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieService.swift; sourceTree = ""; }; - 1F69CE3F297DDCD3005032CE /* StudyRoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyRoomDetailsScreen.swift; sourceTree = ""; }; - 1F69CE41297EC94E005032CE /* DishService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DishService.swift; sourceTree = ""; }; - 1F69CE43297EC97E005032CE /* DishViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DishViewModel.swift; sourceTree = ""; }; - 1F69CE45297EC99D005032CE /* DishView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DishView.swift; sourceTree = ""; }; - 1F69CE47297EDEA3005032CE /* TUMSexyScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TUMSexyScreen.swift; sourceTree = ""; }; - 1F69CE4A297EDEC7005032CE /* TUMSexyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TUMSexyService.swift; sourceTree = ""; }; - 1F69CE4D297EDF12005032CE /* TUMSexyLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TUMSexyLink.swift; sourceTree = ""; }; - 1F71E7F629E4611000379428 /* NavigaTumRoomFinderMaps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumRoomFinderMaps.swift; sourceTree = ""; }; - 1F71E7F729E4611000379428 /* NavigaTumNavigationCoordinates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumNavigationCoordinates.swift; sourceTree = ""; }; - 1F71E7F829E4611000379428 /* NavigaTumOverlaysMaps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumOverlaysMaps.swift; sourceTree = ""; }; - 1F71E7F929E4611000379428 /* NavigaTumNavigationMaps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumNavigationMaps.swift; sourceTree = ""; }; - 1F71E7FA29E4611000379428 /* NavigaTumNavigationAdditionalProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumNavigationAdditionalProperties.swift; sourceTree = ""; }; - 1F71E7FB29E4611000379428 /* NavigaTumOverlayMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumOverlayMap.swift; sourceTree = ""; }; - 1F71E7FC29E4611000379428 /* NavigaTumNavigationDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumNavigationDetails.swift; sourceTree = ""; }; - 1F71E7FE29E4611000379428 /* NavigaTumSearchResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumSearchResponse.swift; sourceTree = ""; }; - 1F71E7FF29E4611000379428 /* NavigaTumSearchResponseSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumSearchResponseSection.swift; sourceTree = ""; }; - 1F71E80029E4611000379428 /* NavigaTumNavigationEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumNavigationEntity.swift; sourceTree = ""; }; - 1F71E80129E4611000379428 /* NavigaTumNavigationProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumNavigationProperty.swift; sourceTree = ""; }; - 1F71E80229E4611000379428 /* NavigaTumRoomFinderMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumRoomFinderMap.swift; sourceTree = ""; }; - 1F71E81529E4611E00379428 /* RoomFinderService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomFinderService.swift; sourceTree = ""; }; - 1F71E81729E4613500379428 /* NavigaTumDetailsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumDetailsViewModel.swift; sourceTree = ""; }; - 1F71E81829E4613500379428 /* NavigaTumViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumViewModel.swift; sourceTree = ""; }; - 1F71E81C29E4613F00379428 /* NavigaTumDetailsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumDetailsView.swift; sourceTree = ""; }; - 1F71E81D29E4613F00379428 /* NavigaTumDetailsBaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumDetailsBaseView.swift; sourceTree = ""; }; - 1F71E81E29E4613F00379428 /* NavigaTumMapView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumMapView.swift; sourceTree = ""; }; - 1F71E81F29E4613F00379428 /* NavigaTumMapImagesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumMapImagesView.swift; sourceTree = ""; }; - 1F71E82029E4613F00379428 /* NavigaTumView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumView.swift; sourceTree = ""; }; - 1F71E82129E4613F00379428 /* NavigaTumListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumListView.swift; sourceTree = ""; }; - 1F71E82C29E464C400379428 /* CafeteriaWidgetViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CafeteriaWidgetViewModel.swift; sourceTree = ""; }; - 1F71E82E29E4667C00379428 /* NavigaTUMAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigaTUMAPI.swift; sourceTree = ""; }; - 1F71E83029E46A1000379428 /* NavigaTUMAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigaTUMAPIError.swift; sourceTree = ""; }; - 1FA538ED297560CD004C70A8 /* MainAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainAPI.swift; sourceTree = ""; }; - 1FACF3F82996A49300A0B8AC /* TUMDevAppAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMDevAppAPIOld.swift; sourceTree = ""; }; - 1FACF3FA2996A65700A0B8AC /* MealPlanService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MealPlanService.swift; sourceTree = ""; }; - 1FACF3FC2996E34200A0B8AC /* MealPlanScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MealPlanScreen.swift; sourceTree = ""; }; + 1F63A3CD29DDE68D00844981 /* TUMSexyLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMSexyLink.swift; sourceTree = ""; }; + 1F63A3CF29DDE68E00844981 /* TUMSexyScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMSexyScreen.swift; sourceTree = ""; }; + 1F63A3D129DDE68E00844981 /* TUMSexyService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMSexyService.swift; sourceTree = ""; }; + 1F63A3D629DDE6D500844981 /* CalendarScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarScreen.swift; sourceTree = ""; }; + 1F63A3D829DDE6D500844981 /* CalendarEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarEvent.swift; sourceTree = ""; }; + 1F63A3D929DDE6D500844981 /* TumCalendarStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TumCalendarStyle.swift; sourceTree = ""; }; + 1F63A3DE29DDE91100844981 /* Confirmation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Confirmation.swift; sourceTree = ""; }; + 1F63A3DF29DDE91100844981 /* Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; + 1F63A3E029DDE91100844981 /* Credentials.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = ""; }; + 1F63A3E529DDF50600844981 /* PersonSearchService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonSearchService.swift; sourceTree = ""; }; + 1F63A3E829DDF50C00844981 /* PersonSearchScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonSearchScreen.swift; sourceTree = ""; }; + 1F63A3EB29DDF97E00844981 /* PersonDetailedScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonDetailedScreen.swift; sourceTree = ""; }; + 1F63A3EE29DDF99600844981 /* LectureSearchService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LectureSearchService.swift; sourceTree = ""; }; + 1F63A3F129DDF99B00844981 /* LectureSearchScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LectureSearchScreen.swift; sourceTree = ""; }; + 1F63A3F429DDFAA100844981 /* PersonDetailedService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonDetailedService.swift; sourceTree = ""; }; + 1F63A3F629DEC67E00844981 /* TuitionScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TuitionScreen.swift; sourceTree = ""; }; + 1F63A3F929DEC6A400844981 /* NewsScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsScreen.swift; sourceTree = ""; }; + 1F63A3FC29DEC6E300844981 /* MovieScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MovieScreen.swift; sourceTree = ""; }; + 1F63A40029DECE0000844981 /* RoomFinderService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomFinderService.swift; sourceTree = ""; }; + 1F63A40229DECFA100844981 /* DishView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DishView.swift; path = "Campus-iOS/MapComponent/View/Cafeterias/DishView.swift"; sourceTree = SOURCE_ROOT; }; + 1F6BD38E296F53E200CDD625 /* DataTypeClassifierV4English.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = DataTypeClassifierV4English.mlmodel; sourceTree = ""; }; + 1F6BD390296F53E900CDD625 /* DataTypeClassifierV4German.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = DataTypeClassifierV4German.mlmodel; sourceTree = ""; }; + 1F71E83429E4800A00379428 /* NavigaTumRoomFinderMaps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumRoomFinderMaps.swift; sourceTree = ""; }; + 1F71E83529E4800A00379428 /* NavigaTumNavigationCoordinates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumNavigationCoordinates.swift; sourceTree = ""; }; + 1F71E83629E4800A00379428 /* NavigaTumOverlaysMaps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumOverlaysMaps.swift; sourceTree = ""; }; + 1F71E83729E4800A00379428 /* NavigaTumNavigationMaps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumNavigationMaps.swift; sourceTree = ""; }; + 1F71E83829E4800A00379428 /* NavigaTumNavigationAdditionalProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumNavigationAdditionalProperties.swift; sourceTree = ""; }; + 1F71E83929E4800A00379428 /* NavigaTumOverlayMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumOverlayMap.swift; sourceTree = ""; }; + 1F71E83A29E4800A00379428 /* NavigaTumNavigationDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumNavigationDetails.swift; sourceTree = ""; }; + 1F71E83C29E4800A00379428 /* NavigaTumSearchResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumSearchResponse.swift; sourceTree = ""; }; + 1F71E83D29E4800A00379428 /* NavigaTumSearchResponseSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumSearchResponseSection.swift; sourceTree = ""; }; + 1F71E83E29E4800A00379428 /* NavigaTumNavigationEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumNavigationEntity.swift; sourceTree = ""; }; + 1F71E83F29E4800A00379428 /* NavigaTumNavigationProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumNavigationProperty.swift; sourceTree = ""; }; + 1F71E84029E4800A00379428 /* NavigaTumRoomFinderMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumRoomFinderMap.swift; sourceTree = ""; }; + 1F71E84D29E4802800379428 /* NavigaTumDetailsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumDetailsViewModel.swift; sourceTree = ""; }; + 1F71E84E29E4802800379428 /* NavigaTumViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumViewModel.swift; sourceTree = ""; }; + 1F71E85229E4803400379428 /* NavigaTumDetailsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumDetailsView.swift; sourceTree = ""; }; + 1F71E85329E4803400379428 /* NavigaTumDetailsBaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumDetailsBaseView.swift; sourceTree = ""; }; + 1F71E85429E4803400379428 /* NavigaTumMapView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumMapView.swift; sourceTree = ""; }; + 1F71E85529E4803400379428 /* NavigaTumMapImagesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumMapImagesView.swift; sourceTree = ""; }; + 1F71E85629E4803400379428 /* NavigaTumView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumView.swift; sourceTree = ""; }; + 1F71E85729E4803400379428 /* NavigaTumListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTumListView.swift; sourceTree = ""; }; + 1F71E85E29E4833500379428 /* NavigaTUMAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTUMAPI.swift; sourceTree = ""; }; + 1F71E86029E4834200379428 /* NavigaTUMAPIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigaTUMAPIError.swift; sourceTree = ""; }; + 1F74DB2C2971D0D200B9143C /* NewsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsService.swift; sourceTree = ""; }; + 1F74DB2E2971E01100B9143C /* RoomFinderSearchResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomFinderSearchResultViewModel.swift; sourceTree = ""; }; + 1F74DB302971E01F00B9143C /* RoomFinderSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomFinderSearchResultView.swift; sourceTree = ""; }; + 1F74DB322972B1C600B9143C /* LectureSearchResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LectureSearchResultViewModel.swift; sourceTree = ""; }; + 1F74DB342972B22F00B9143C /* LectureSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LectureSearchResultView.swift; sourceTree = ""; }; + 1F74DB362972B87800B9143C /* PersonSearchResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonSearchResultViewModel.swift; sourceTree = ""; }; + 1F74DB382972B92D00B9143C /* PersonSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonSearchResultView.swift; sourceTree = ""; }; + 1F74DB3A2973038800B9143C /* MovieService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieService.swift; sourceTree = ""; }; + 1FAC1A6E2A0632F900A0A4F3 /* View+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Search.swift"; sourceTree = ""; }; + 1FAC1A712A0644B600A0A4F3 /* ExpandIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandIcon.swift; sourceTree = ""; }; 1FAF9F0B284D2ABC000ABE93 /* MapScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapScreenView.swift; sourceTree = ""; }; 1FB82E3328F95776007B1858 /* TokenPermissionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenPermissionsView.swift; sourceTree = ""; }; 1FB82E3528F96C9E007B1858 /* TokenPermissionsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenPermissionsViewModel.swift; sourceTree = ""; }; 1FBFA167285E5B2D00FC1515 /* PanelContentListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanelContentListView.swift; sourceTree = ""; }; - 1FFF9AC5297D31830098E874 /* ProfileService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileService.swift; sourceTree = ""; }; + 1FD025D12971A31300E66981 /* NewsSearchResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsSearchResultViewModel.swift; sourceTree = ""; }; + 1FD025D62971BD2000E66981 /* NewsSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsSearchResultView.swift; sourceTree = ""; }; + 1FE6DC9E29B74ACA006A06B9 /* GradeSearchResultScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradeSearchResultScreen.swift; sourceTree = ""; }; + 1FE6DCA129B77012006A06B9 /* SearchResultErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultErrorView.swift; sourceTree = ""; }; + 1FE6DCA329B77094006A06B9 /* SearchResultLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultLoadingView.swift; sourceTree = ""; }; + 1FE6DCA629B77978006A06B9 /* LectureSearchResultScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LectureSearchResultScreen.swift; sourceTree = ""; }; + 1FE6DCA929B78131006A06B9 /* CafeteriaSearchResultScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CafeteriaSearchResultScreen.swift; sourceTree = ""; }; + 1FE6DCAB29B790F8006A06B9 /* SearchError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchError.swift; sourceTree = ""; }; + 1FE6DCAE29B79280006A06B9 /* StudyRoomSearchResultScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyRoomSearchResultScreen.swift; sourceTree = ""; }; + 1FE6DCB129B79B6E006A06B9 /* EventSearchResultScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventSearchResultScreen.swift; sourceTree = ""; }; + 1FE6DCB429B79FDF006A06B9 /* MovieSearchResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieSearchResultViewModel.swift; sourceTree = ""; }; + 1FE6DCB629B7A2B0006A06B9 /* MovieSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieSearchResultView.swift; sourceTree = ""; }; + 1FE6DCB929B7A7FE006A06B9 /* MovieSearchResultScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieSearchResultScreen.swift; sourceTree = ""; }; + 1FE6DCBB29B7A824006A06B9 /* NewsSearchResultScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsSearchResultScreen.swift; sourceTree = ""; }; + 1FE6DCBE29B7AB76006A06B9 /* RoomFinderSearchResultScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomFinderSearchResultScreen.swift; sourceTree = ""; }; + 1FE6DCC129B7ADB7006A06B9 /* PersonSearchResultScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonSearchResultScreen.swift; sourceTree = ""; }; 227FBB492762AC440062FEC3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 227FBB4A2762AC4C0062FEC3 /* Campus-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Campus-iOS.entitlements"; sourceTree = ""; }; 256D0D4227D77A9C00F5EC38 /* MapViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewModel.swift; sourceTree = ""; }; @@ -486,17 +612,17 @@ 36108BCF27A304B5007DC62D /* DishLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DishLabel.swift; sourceTree = ""; }; 36108BD227A304B5007DC62D /* MealPlanView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealPlanView.swift; sourceTree = ""; }; 36108BD327A304B5007DC62D /* MealPlanViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealPlanViewModel.swift; sourceTree = ""; }; - 36108BD427A304B5007DC62D /* MenuViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuViewModel.swift; sourceTree = ""; }; + 36108BD427A304B5007DC62D /* Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Menu.swift; sourceTree = ""; }; 36108BD627A304B5007DC62D /* MenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = ""; }; 36108BD927A304B5007DC62D /* CafeteriaRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CafeteriaRowView.swift; sourceTree = ""; }; 36108BDD27A304B5007DC62D /* MapContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapContentView.swift; sourceTree = ""; }; 36108BDE27A304B5007DC62D /* PanelContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanelContentView.swift; sourceTree = ""; }; 36108BF327A30516007DC62D /* Movie.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Movie.swift; sourceTree = ""; }; - 36108BF427A30516007DC62D /* MoviesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoviesViewModel.swift; sourceTree = ""; }; + 36108BF427A30516007DC62D /* MovieViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MovieViewModel.swift; sourceTree = ""; }; 36108BF627A30516007DC62D /* MovieCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MovieCard.swift; sourceTree = ""; }; 36108BF727A30516007DC62D /* MovieDetailedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MovieDetailedView.swift; sourceTree = ""; }; 36108BF827A30516007DC62D /* MovieDetailCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MovieDetailCellView.swift; sourceTree = ""; }; - 36108BF927A30516007DC62D /* MoviesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoviesView.swift; sourceTree = ""; }; + 36108BF927A30516007DC62D /* MovieView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MovieView.swift; sourceTree = ""; }; 36108C0127A30762007DC62D /* Enums.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Enums.swift; sourceTree = ""; }; 36108C0527A307F9007DC62D /* GradeColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradeColor.swift; sourceTree = ""; }; 36108C0627A307F9007DC62D /* GradesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradesViewModel.swift; sourceTree = ""; }; @@ -516,7 +642,6 @@ 36203E802761B9F200C24658 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = "Campus-iOS/de.lproj/Localizable.strings"; sourceTree = ""; }; 36203E882761C6EC00C24658 /* TUMSplashScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMSplashScreen.swift; sourceTree = ""; }; 36203E892761C6EC00C24658 /* LoginView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; - 36203E8A2761C6EC00C24658 /* Credentials.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = ""; }; 3629BA2B27A1CECA0036AC80 /* NewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsView.swift; sourceTree = ""; }; 3629BA2D27A1CEFA0036AC80 /* NewsCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsCard.swift; sourceTree = ""; }; 3629BA3027A1D0AD0036AC80 /* ScrollableCardsViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableCardsViewModifier.swift; sourceTree = ""; }; @@ -561,22 +686,10 @@ 36982BD727A2739000515847 /* Collapsible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collapsible.swift; sourceTree = ""; }; 3698CBEC2761E014001C5735 /* CustomRoundedBorderTextFieldStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomRoundedBorderTextFieldStyle.swift; sourceTree = ""; }; 3698CBEE2761E6CC001C5735 /* TokenConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenConfirmationView.swift; sourceTree = ""; }; - 36AD5CF127B7FEAD00DAE143 /* TumCalendarStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TumCalendarStyle.swift; sourceTree = ""; }; 36AD5CF327B8C83500DAE143 /* CalendarSingleEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarSingleEventView.swift; sourceTree = ""; }; 36AD5CF527B8D97500DAE143 /* LectureDetailsEventInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LectureDetailsEventInfoView.swift; sourceTree = ""; }; - 36AD5CF727B96AD200DAE143 /* PersonSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonSearchView.swift; sourceTree = ""; }; - 36AD5CF927B9711B00DAE143 /* LectureSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LectureSearchView.swift; sourceTree = ""; }; - 36AD5CFB27B974F100DAE143 /* TuitionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuitionScreen.swift; sourceTree = ""; }; 36AD5CFD27BA064E00DAE143 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; - 36AF61BB27A2FD7700FEBD98 /* TUMSexyAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMSexyAPIOld.swift; sourceTree = ""; }; - 36AF61BC27A2FD7700FEBD98 /* EatAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EatAPIOld.swift; sourceTree = ""; }; - 36AF61BD27A2FD7700FEBD98 /* MVGAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MVGAPIOld.swift; sourceTree = ""; }; - 36AF61BE27A2FD7700FEBD98 /* NetworkingAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkingAPIOld.swift; sourceTree = ""; }; - 36AF61BF27A2FD7700FEBD98 /* CampusOnlineAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CampusOnlineAPIOld.swift; sourceTree = ""; }; - 36AF61C027A2FD7700FEBD98 /* APIResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIResponse.swift; sourceTree = ""; }; - 36AF61C127A2FD7700FEBD98 /* TUMOnlineAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMOnlineAPIOld.swift; sourceTree = ""; }; 36AF61C227A2FD7700FEBD98 /* Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = ""; }; - 36AF61C327A2FD7700FEBD98 /* TUMCabeAPIOld.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TUMCabeAPIOld.swift; sourceTree = ""; }; 36AF61C527A2FD7700FEBD98 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 36AF61C627A2FD7700FEBD98 /* APIConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIConstants.swift; sourceTree = ""; }; 36AF61C827A2FD7700FEBD98 /* NetworkingError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkingError.swift; sourceTree = ""; }; @@ -596,7 +709,7 @@ 36BB6F5227AFCCB500F224AB /* PersonDetailedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonDetailedView.swift; sourceTree = ""; }; 36BB6F5B27AFCDF900F224AB /* PersonSearchViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonSearchViewModel.swift; sourceTree = ""; }; 36BB6F5D27AFCDFA00F224AB /* Person.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Person.swift; sourceTree = ""; }; - 36BB6F5F27AFCDFA00F224AB /* PersonSearchScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonSearchScreen.swift; sourceTree = ""; }; + 36BB6F5F27AFCDFA00F224AB /* PersonSearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonSearchView.swift; sourceTree = ""; }; 36BB6F6327AFCFFB00F224AB /* PersonDetailedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonDetailedViewModel.swift; sourceTree = ""; }; 36BB6F6527AFD12B00F224AB /* PersonDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonDetails.swift; sourceTree = ""; }; 36BB6F6727AFD26500F224AB /* Organization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Organization.swift; sourceTree = ""; }; @@ -608,7 +721,7 @@ 36BB6F7827B26DE300F224AB /* TuitionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuitionView.swift; sourceTree = ""; }; 36BB6F7A27B27D0D00F224AB /* TuitionCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuitionCard.swift; sourceTree = ""; }; 36BB6F7E27B386D100F224AB /* AddToContactsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToContactsView.swift; sourceTree = ""; }; - 36BB6F8227B39B4300F224AB /* LectureSearchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LectureSearchScreen.swift; sourceTree = ""; }; + 36BB6F8227B39B4300F224AB /* LectureSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LectureSearchView.swift; sourceTree = ""; }; 36BB6F8527B39C5300F224AB /* LectureSearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LectureSearchViewModel.swift; sourceTree = ""; }; 36BB6F8C27B3F25A00F224AB /* NSMutableString+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMutableString+Extensions.swift"; sourceTree = ""; }; 36BBE72E27989F8C0018FD3F /* SFSafariViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SFSafariViewWrapper.swift; sourceTree = ""; }; @@ -636,11 +749,6 @@ 97270F5927AB2A4900BB25E4 /* Array+Rearrange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Rearrange.swift"; sourceTree = ""; }; 974D5B9927E5E9CB00FD7B11 /* GlowBorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlowBorder.swift; sourceTree = ""; }; 97F8A79227E641570099EE83 /* AcademicDegree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcademicDegree.swift; sourceTree = ""; }; - 9926FD422A1D0BC000EE3ECB /* GradesStudyProgramView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradesStudyProgramView.swift; sourceTree = ""; }; - 9926FD442A1D0DBC00EE3ECB /* GradesSemesterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradesSemesterView.swift; sourceTree = ""; }; - 9970686F298569E10028D235 /* CrashlyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashlyticsService.swift; sourceTree = ""; }; - 99EE85332A0920AE00973916 /* AverageGradesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AverageGradesService.swift; sourceTree = ""; }; - 99EE85352A09216600973916 /* AverageGrade.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AverageGrade.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -758,7 +866,7 @@ 100803442764E2A90013ED0E /* ProfileComponent */ = { isa = PBXGroup; children = ( - 1FFF9AC4297D317C0098E874 /* Service */, + 1F26159329D9E8F10062B8E5 /* Service */, 36BB6F7127B1CD8100F224AB /* ViewModel */, 36BB6F6E27B1196300F224AB /* Entity */, 36BB6F6D27B1195600F224AB /* View */, @@ -766,142 +874,196 @@ path = ProfileComponent; sourceTree = ""; }; - 1F04F16F297AA1E00085F273 /* Old APIs */ = { + 1F0E8EB9293E22D4006E8BB9 /* DataModels */ = { isa = PBXGroup; children = ( - 36AF61C027A2FD7700FEBD98 /* APIResponse.swift */, - 36AF61BB27A2FD7700FEBD98 /* TUMSexyAPIOld.swift */, - 1FACF3F82996A49300A0B8AC /* TUMDevAppAPIOld.swift */, - 36AF61BC27A2FD7700FEBD98 /* EatAPIOld.swift */, - 36AF61BD27A2FD7700FEBD98 /* MVGAPIOld.swift */, - 36AF61BE27A2FD7700FEBD98 /* NetworkingAPIOld.swift */, - 36AF61BF27A2FD7700FEBD98 /* CampusOnlineAPIOld.swift */, - 36AF61C127A2FD7700FEBD98 /* TUMOnlineAPIOld.swift */, - 36AF61C327A2FD7700FEBD98 /* TUMCabeAPIOld.swift */, + 1F6BD389296F4F8E00CDD625 /* Old models */, + 1F6BD390296F53E900CDD625 /* DataTypeClassifierV4German.mlmodel */, + 1F6BD38E296F53E200CDD625 /* DataTypeClassifierV4English.mlmodel */, + ); + path = DataModels; + sourceTree = ""; + }; + 1F0F396D2A058B3500E55FCB /* Model */ = { + isa = PBXGroup; + children = ( + 36108BF327A30516007DC62D /* Movie.swift */, + 1F0F396E2A058B5000E55FCB /* Movie+PreviewData.swift */, + ); + path = Model; + sourceTree = ""; + }; + 1F26155D29D9E8BD0062B8E5 /* Protocols */ = { + isa = PBXGroup; + children = ( + 1F26155E29D9E8BD0062B8E5 /* APIError.swift */, + 1F26155F29D9E8BD0062B8E5 /* Service.swift */, + 1F26156029D9E8BD0062B8E5 /* MainAPI.swift */, + 1F26156129D9E8BD0062B8E5 /* API.swift */, + ); + path = Protocols; + sourceTree = ""; + }; + 1F26156629D9E8DC0062B8E5 /* Old APIs */ = { + isa = PBXGroup; + children = ( + 1F26156729D9E8DC0062B8E5 /* TUMDevAppAPIOld.swift */, + 1F26156829D9E8DC0062B8E5 /* EatAPIOld.swift */, + 1F26156929D9E8DC0062B8E5 /* MVGAPIOld.swift */, + 1F26156A29D9E8DC0062B8E5 /* CampusOnlineAPIOld.swift */, + 1F26156B29D9E8DC0062B8E5 /* TUMCabeAPIOld.swift */, + 1F26156C29D9E8DC0062B8E5 /* TUMSexyAPIOld.swift */, + 1F26156D29D9E8DC0062B8E5 /* TUMOnlineAPIOld.swift */, + 1F26156E29D9E8DC0062B8E5 /* APIResponse.swift */, + 1F26156F29D9E8DC0062B8E5 /* NetworkingAPIOld.swift */, ); path = "Old APIs"; sourceTree = ""; }; - 1F04F176297AD42E0085F273 /* Screen */ = { + 1F26157029D9E8DC0062B8E5 /* APIErrors */ = { isa = PBXGroup; children = ( - 1F04F174297AD4280085F273 /* CalendarScreen.swift */, + 1F26157129D9E8DC0062B8E5 /* TUMDevAppAPIError.swift */, + 1F26157229D9E8DC0062B8E5 /* TUMOnlineAPIError.swift */, + 1F26157329D9E8DC0062B8E5 /* EatAPIError.swift */, + 1F26157429D9E8DC0062B8E5 /* TUMCabeAPIError.swift */, + 1F26157529D9E8DC0062B8E5 /* MVGAPIError.swift */, + 1F26157629D9E8DC0062B8E5 /* TUMSexyAPIError.swift */, + 1F71E86029E4834200379428 /* NavigaTUMAPIError.swift */, ); - path = Screen; + path = APIErrors; sourceTree = ""; }; - 1F04F177297AD4350085F273 /* Service */ = { + 1F26157729D9E8DC0062B8E5 /* APIs */ = { isa = PBXGroup; children = ( - 1F04F16D297A9A700085F273 /* CalendarService.swift */, + 1F26157829D9E8DC0062B8E5 /* TUMSexyAPI.swift */, + 1F26157929D9E8DC0062B8E5 /* EatAPI.swift */, + 1F26157A29D9E8DC0062B8E5 /* TUMDevAppAPI.swift */, + 1F26157B29D9E8DC0062B8E5 /* MVGAPI.swift */, + 1F26157C29D9E8DC0062B8E5 /* TUMOnlineAPI.swift */, + 1F26157D29D9E8DC0062B8E5 /* TUMCabeAPI.swift */, + 1F71E85E29E4833500379428 /* NavigaTUMAPI.swift */, ); - path = Service; + path = APIs; sourceTree = ""; }; - 1F04F17A297AED190085F273 /* Service */ = { + 1F26159329D9E8F10062B8E5 /* Service */ = { isa = PBXGroup; children = ( - 1F04F178297AED150085F273 /* LectureSearchService.swift */, + 1F26159429D9E8F10062B8E5 /* ProfileService.swift */, ); path = Service; sourceTree = ""; }; - 1F04F17B297B0BC30085F273 /* Screen */ = { + 1F26159629D9E9010062B8E5 /* Crashlytics */ = { isa = PBXGroup; children = ( - 36BB6F8227B39B4300F224AB /* LectureSearchScreen.swift */, + 1F26159729D9E9010062B8E5 /* CrashlyticsService.swift */, ); - path = Screen; + path = Crashlytics; sourceTree = ""; }; - 1F04F180297BDF250085F273 /* Service */ = { + 1F4C836528300E6F006971C0 /* Service */ = { isa = PBXGroup; children = ( - 1F04F17E297BDF1E0085F273 /* PersonSearchService.swift */, + 1F26159B29DA015D0062B8E5 /* DishService.swift */, + 1F26159C29DA015D0062B8E5 /* MealPlanService.swift */, + 1F4C836628300E79006971C0 /* CafeteriasService.swift */, + 3654F357285167C3008AD5DC /* StudyRoomsService.swift */, ); path = Service; sourceTree = ""; }; - 1F04F181297C33860085F273 /* Screen */ = { + 1F504173295B82FE00A62CA1 /* SearchComponent */ = { isa = PBXGroup; children = ( - 36BB6F5F27AFCDFA00F224AB /* PersonSearchScreen.swift */, + 1F504183295B84CB00A62CA1 /* Types and Protocols */, + 1F504174295B830900A62CA1 /* Views */, + 1F504179295B840200A62CA1 /* ViewModels */, ); - path = Screen; + path = SearchComponent; sourceTree = ""; }; - 1F04F186297C3FA20085F273 /* Service */ = { + 1F504174295B830900A62CA1 /* Views */ = { isa = PBXGroup; children = ( - 1F04F184297C3F990085F273 /* PersonDetailedService.swift */, + 1F504171295B82F900A62CA1 /* SearchView.swift */, + 1F504175295B83AA00A62CA1 /* SearchResultView.swift */, + 1FAC1A702A0644A500A0A4F3 /* Extensions and Custom Views */, + 1F50417C295B844F00A62CA1 /* SearchResultViews */, + 1FE6DCA029B76FB7006A06B9 /* Additional Views */, ); - path = Service; + path = Views; sourceTree = ""; }; - 1F04F187297C6BC40085F273 /* Screen */ = { + 1F504179295B840200A62CA1 /* ViewModels */ = { isa = PBXGroup; children = ( - 1F04F182297C3EF70085F273 /* PersonDetailedScreen.swift */, + 1F504177295B83FC00A62CA1 /* SearchResultViewModel.swift */, + 1F504188295B851A00A62CA1 /* Searchable ViewModels */, ); - path = Screen; + path = ViewModels; sourceTree = ""; }; - 1F04F188297C85030085F273 /* Model */ = { + 1F50417C295B844F00A62CA1 /* SearchResultViews */ = { isa = PBXGroup; children = ( - 36203E8A2761C6EC00C24658 /* Credentials.swift */, - 1F04F189297C85120085F273 /* Token.swift */, - 1F04F18B297C85190085F273 /* Confirmation.swift */, + 1FE6DCA829B78118006A06B9 /* Cafeteria */, + 1FE6DC9D29B73B61006A06B9 /* Grade */, + 1FE6DCAD29B79180006A06B9 /* StudyRoom */, + 1FE6DCB029B79B5D006A06B9 /* Event */, + 1FE6DCB329B79EC1006A06B9 /* News */, + 1FE6DCBD29B7AB5E006A06B9 /* RoomFinder */, + 1FE6DCA529B77956006A06B9 /* Lecture */, + 1FE6DCC029B7ABCD006A06B9 /* Person */, + 1FE6DCB829B7A7F2006A06B9 /* Movie */, ); - path = Model; + path = SearchResultViews; sourceTree = ""; }; - 1F189E8B29968CD70056BBD8 /* APIs */ = { + 1F504183295B84CB00A62CA1 /* Types and Protocols */ = { isa = PBXGroup; children = ( - 1F189E8C29968CE50056BBD8 /* TUMOnlineAPI.swift */, - 1F189E8E29968CFC0056BBD8 /* TUMCabeAPI.swift */, - 1F189E9029968D130056BBD8 /* EatAPI.swift */, - 1F189E9229968D260056BBD8 /* TUMDevAppAPI.swift */, - 1F189E9429968D330056BBD8 /* TUMSexyAPI.swift */, - 1F189E9629968D490056BBD8 /* MVGAPI.swift */, - 1F71E82E29E4667C00379428 /* NavigaTUMAPI.swift */, + 1F50418B295B854900A62CA1 /* GlobalSearch.swift */, + 1F504186295B84F400A62CA1 /* Searchable.swift */, + 1F504184295B84DC00A62CA1 /* ComparisonToken.swift */, + 1F504191295B85D300A62CA1 /* Extensions */, + 1FE6DCAB29B790F8006A06B9 /* SearchError.swift */, + 1F0F39762A05975200E55FCB /* SearchState.swift */, ); - path = APIs; + path = "Types and Protocols"; sourceTree = ""; }; - 1F189E9829968D620056BBD8 /* APIErrors */ = { + 1F504188295B851A00A62CA1 /* Searchable ViewModels */ = { isa = PBXGroup; children = ( - 1F189E9929968D790056BBD8 /* TUMOnlineAPIError.swift */, - 1F189E9B29968D880056BBD8 /* TUMCabeAPIError.swift */, - 1F189E9D29968D9B0056BBD8 /* EatAPIError.swift */, - 1F189E9F29968DA90056BBD8 /* TUMDevAppAPIError.swift */, - 1F189EA129968DB90056BBD8 /* TUMSexyAPIError.swift */, - 1F189EA329968DE60056BBD8 /* MVGAPIError.swift */, - 1F71E83029E46A1000379428 /* NavigaTUMAPIError.swift */, + 1F50417D295B848000A62CA1 /* CafeteriaSearchResultViewModel.swift */, + 1F504189295B852F00A62CA1 /* GradeSearchResultViewModel.swift */, + 1F504194295B898000A62CA1 /* StudyRoomSearchResultViewModel.swift */, + 1F504198295CC24E00A62CA1 /* EventSearchResultViewModel.swift */, + 1FD025D12971A31300E66981 /* NewsSearchResultViewModel.swift */, + 1FE6DCB429B79FDF006A06B9 /* MovieSearchResultViewModel.swift */, + 1F74DB2E2971E01100B9143C /* RoomFinderSearchResultViewModel.swift */, + 1F74DB322972B1C600B9143C /* LectureSearchResultViewModel.swift */, + 1F74DB362972B87800B9143C /* PersonSearchResultViewModel.swift */, ); - path = APIErrors; + path = "Searchable ViewModels"; sourceTree = ""; }; - 1F189EA529968E150056BBD8 /* Protocols */ = { + 1F504191295B85D300A62CA1 /* Extensions */ = { isa = PBXGroup; children = ( - 1FA538ED297560CD004C70A8 /* MainAPI.swift */, - 1F189EA629968E5C0056BBD8 /* API.swift */, - 1F183A162979D19000B5D22D /* APIError.swift */, - 1F04F170297AA5F30085F273 /* Service.swift */, + 1F50418D295B857B00A62CA1 /* String+Levenshtein.swift */, + 1F50418F295B85AB00A62CA1 /* String+Keep.swift */, ); - path = Protocols; + path = Extensions; sourceTree = ""; }; - 1F4C836528300E6F006971C0 /* Service */ = { + 1F50419E295D862100A62CA1 /* Service */ = { isa = PBXGroup; children = ( - 1F4C836628300E79006971C0 /* CafeteriasService.swift */, - 3654F357285167C3008AD5DC /* StudyRoomsService.swift */, - 1F69CE41297EC94E005032CE /* DishService.swift */, - 1FACF3FA2996A65700A0B8AC /* MealPlanService.swift */, + 1F50419C295D861D00A62CA1 /* CalendarService.swift */, ); path = Service; sourceTree = ""; @@ -914,134 +1076,320 @@ path = VideoAssets; sourceTree = ""; }; - 1F69CE33297DB729005032CE /* Service */ = { + 1F59CA0729E34B8B00EA3BA1 /* Model */ = { isa = PBXGroup; children = ( - 1F69CE34297DB732005032CE /* NewsService.swift */, + 36BB6F7427B1D87200F224AB /* Tuition.swift */, + ); + path = Model; + sourceTree = ""; + }; + 1F63A3CC29DDE68D00844981 /* Model */ = { + isa = PBXGroup; + children = ( + 1F63A3CD29DDE68D00844981 /* TUMSexyLink.swift */, + ); + path = Model; + sourceTree = ""; + }; + 1F63A3CE29DDE68E00844981 /* Screen */ = { + isa = PBXGroup; + children = ( + 1F63A3CF29DDE68E00844981 /* TUMSexyScreen.swift */, + ); + path = Screen; + sourceTree = ""; + }; + 1F63A3D029DDE68E00844981 /* Service */ = { + isa = PBXGroup; + children = ( + 1F63A3D129DDE68E00844981 /* TUMSexyService.swift */, ); path = Service; sourceTree = ""; }; - 1F69CE38297DCA2B005032CE /* NewsScreen */ = { + 1F63A3D529DDE6D500844981 /* Screen */ = { + isa = PBXGroup; + children = ( + 1F63A3D629DDE6D500844981 /* CalendarScreen.swift */, + ); + path = Screen; + sourceTree = ""; + }; + 1F63A3D729DDE6D500844981 /* Model */ = { + isa = PBXGroup; + children = ( + 1F63A3D829DDE6D500844981 /* CalendarEvent.swift */, + 1F0F39742A05917400E55FCB /* CalendarEvent+PreviewData.swift */, + 1F63A3D929DDE6D500844981 /* TumCalendarStyle.swift */, + ); + path = Model; + sourceTree = ""; + }; + 1F63A3DD29DDE91100844981 /* Model */ = { isa = PBXGroup; children = ( - 1F69CE36297DCA22005032CE /* NewsScreen.swift */, + 1F63A3DE29DDE91100844981 /* Confirmation.swift */, + 1F63A3DF29DDE91100844981 /* Token.swift */, + 1F63A3E029DDE91100844981 /* Credentials.swift */, ); - path = NewsScreen; + path = Model; sourceTree = ""; }; - 1F69CE39297DCBFE005032CE /* Service */ = { + 1F63A3E429DDF50600844981 /* Service */ = { isa = PBXGroup; children = ( - 1F69CE3D297DCC19005032CE /* MovieService.swift */, + 1F63A3E529DDF50600844981 /* PersonSearchService.swift */, ); path = Service; sourceTree = ""; }; - 1F69CE3A297DCC04005032CE /* Screen */ = { + 1F63A3E729DDF50C00844981 /* Screen */ = { isa = PBXGroup; children = ( - 1F69CE3B297DCC12005032CE /* MoviesScreen.swift */, + 1F63A3E829DDF50C00844981 /* PersonSearchScreen.swift */, ); path = Screen; sourceTree = ""; }; - 1F69CE49297EDEB5005032CE /* Screen */ = { + 1F63A3EA29DDF97E00844981 /* Screen */ = { isa = PBXGroup; children = ( - 1F69CE47297EDEA3005032CE /* TUMSexyScreen.swift */, + 1F63A3EB29DDF97E00844981 /* PersonDetailedScreen.swift */, ); path = Screen; sourceTree = ""; }; - 1F69CE4C297EDECA005032CE /* Service */ = { + 1F63A3ED29DDF99600844981 /* Service */ = { isa = PBXGroup; children = ( - 1F69CE4A297EDEC7005032CE /* TUMSexyService.swift */, + 1F63A3EE29DDF99600844981 /* LectureSearchService.swift */, ); path = Service; sourceTree = ""; }; - 1F69CE4F297EDF32005032CE /* Model */ = { + 1F63A3F029DDF99B00844981 /* Screen */ = { isa = PBXGroup; children = ( - 1F69CE4D297EDF12005032CE /* TUMSexyLink.swift */, + 1F63A3F129DDF99B00844981 /* LectureSearchScreen.swift */, ); - path = Model; + path = Screen; + sourceTree = ""; + }; + 1F63A3F329DDFAA100844981 /* Service */ = { + isa = PBXGroup; + children = ( + 1F63A3F429DDFAA100844981 /* PersonDetailedService.swift */, + ); + path = Service; + sourceTree = ""; + }; + 1F63A3F829DEC6A400844981 /* Screen */ = { + isa = PBXGroup; + children = ( + 1F63A3F929DEC6A400844981 /* NewsScreen.swift */, + ); + path = Screen; + sourceTree = ""; + }; + 1F63A3FB29DEC6E300844981 /* Screen */ = { + isa = PBXGroup; + children = ( + 1F63A3FC29DEC6E300844981 /* MovieScreen.swift */, + ); + path = Screen; + sourceTree = ""; + }; + 1F63A3FF29DECDE200844981 /* Service */ = { + isa = PBXGroup; + children = ( + 1F63A40029DECE0000844981 /* RoomFinderService.swift */, + ); + path = Service; + sourceTree = ""; + }; + 1F6BD389296F4F8E00CDD625 /* Old models */ = { + isa = PBXGroup; + children = ( + 1F36EC8129377CD300349FC1 /* DataTypeClassifierV1.mlmodel */, + 1F0E8EBA293E22FF006E8BB9 /* DataTypeClassifierV2.mlmodel */, + 1F488335296EF0CD0002CE77 /* DataTypeClassifierV3.mlmodel */, + ); + path = "Old models"; sourceTree = ""; }; - 1F71E7F429E4611000379428 /* Model */ = { + 1F71E83229E4800A00379428 /* Model */ = { isa = PBXGroup; children = ( - 1F71E7F529E4611000379428 /* Details */, - 1F71E7FB29E4611000379428 /* NavigaTumOverlayMap.swift */, - 1F71E7FC29E4611000379428 /* NavigaTumNavigationDetails.swift */, - 1F71E7FD29E4611000379428 /* Search */, - 1F71E80029E4611000379428 /* NavigaTumNavigationEntity.swift */, - 1F71E80129E4611000379428 /* NavigaTumNavigationProperty.swift */, - 1F71E80229E4611000379428 /* NavigaTumRoomFinderMap.swift */, + 1F71E83329E4800A00379428 /* Details */, + 1F71E83929E4800A00379428 /* NavigaTumOverlayMap.swift */, + 1F71E83A29E4800A00379428 /* NavigaTumNavigationDetails.swift */, + 1F71E83B29E4800A00379428 /* Search */, + 1F71E83E29E4800A00379428 /* NavigaTumNavigationEntity.swift */, + 1F71E83F29E4800A00379428 /* NavigaTumNavigationProperty.swift */, + 1F71E84029E4800A00379428 /* NavigaTumRoomFinderMap.swift */, ); path = Model; sourceTree = ""; }; - 1F71E7F529E4611000379428 /* Details */ = { + 1F71E83329E4800A00379428 /* Details */ = { isa = PBXGroup; children = ( - 1F71E7F629E4611000379428 /* NavigaTumRoomFinderMaps.swift */, - 1F71E7F729E4611000379428 /* NavigaTumNavigationCoordinates.swift */, - 1F71E7F829E4611000379428 /* NavigaTumOverlaysMaps.swift */, - 1F71E7F929E4611000379428 /* NavigaTumNavigationMaps.swift */, - 1F71E7FA29E4611000379428 /* NavigaTumNavigationAdditionalProperties.swift */, + 1F71E83429E4800A00379428 /* NavigaTumRoomFinderMaps.swift */, + 1F71E83529E4800A00379428 /* NavigaTumNavigationCoordinates.swift */, + 1F71E83629E4800A00379428 /* NavigaTumOverlaysMaps.swift */, + 1F71E83729E4800A00379428 /* NavigaTumNavigationMaps.swift */, + 1F71E83829E4800A00379428 /* NavigaTumNavigationAdditionalProperties.swift */, ); path = Details; sourceTree = ""; }; - 1F71E7FD29E4611000379428 /* Search */ = { + 1F71E83B29E4800A00379428 /* Search */ = { isa = PBXGroup; children = ( - 1F71E7FE29E4611000379428 /* NavigaTumSearchResponse.swift */, - 1F71E7FF29E4611000379428 /* NavigaTumSearchResponseSection.swift */, + 1F71E83C29E4800A00379428 /* NavigaTumSearchResponse.swift */, + 1F71E83D29E4800A00379428 /* NavigaTumSearchResponseSection.swift */, ); path = Search; sourceTree = ""; }; - 1F71E81429E4611E00379428 /* Service */ = { + 1F71E85129E4803400379428 /* ViewNavigaTum */ = { isa = PBXGroup; children = ( - 1F71E81529E4611E00379428 /* RoomFinderService.swift */, + 1F71E85229E4803400379428 /* NavigaTumDetailsView.swift */, + 1F71E85329E4803400379428 /* NavigaTumDetailsBaseView.swift */, + 1F71E85429E4803400379428 /* NavigaTumMapView.swift */, + 1F71E85529E4803400379428 /* NavigaTumMapImagesView.swift */, + 1F71E85629E4803400379428 /* NavigaTumView.swift */, + 1F71E85729E4803400379428 /* NavigaTumListView.swift */, ); - path = Service; + path = ViewNavigaTum; sourceTree = ""; }; - 1F71E81B29E4613F00379428 /* ViewNavigaTum */ = { + 1F74DB3C2973039200B9143C /* Service */ = { isa = PBXGroup; children = ( - 1F71E81C29E4613F00379428 /* NavigaTumDetailsView.swift */, - 1F71E81D29E4613F00379428 /* NavigaTumDetailsBaseView.swift */, - 1F71E81E29E4613F00379428 /* NavigaTumMapView.swift */, - 1F71E81F29E4613F00379428 /* NavigaTumMapImagesView.swift */, - 1F71E82029E4613F00379428 /* NavigaTumView.swift */, - 1F71E82129E4613F00379428 /* NavigaTumListView.swift */, + 1F74DB3A2973038800B9143C /* MovieService.swift */, ); - path = ViewNavigaTum; + path = Service; sourceTree = ""; }; - 1FFEF086284E417E00ADD201 /* Recovered References */ = { + 1FAC1A702A0644A500A0A4F3 /* Extensions and Custom Views */ = { isa = PBXGroup; children = ( - 256D0D4227D77A9C00F5EC38 /* MapViewModel.swift */, + 1FAC1A6E2A0632F900A0A4F3 /* View+Search.swift */, + 1FAC1A712A0644B600A0A4F3 /* ExpandIcon.swift */, ); - name = "Recovered References"; + path = "Extensions and Custom Views"; sourceTree = ""; }; - 1FFF9AC4297D317C0098E874 /* Service */ = { + 1FD025D52971AB8C00E66981 /* Service */ = { isa = PBXGroup; children = ( - 1FFF9AC5297D31830098E874 /* ProfileService.swift */, + 1F74DB2C2971D0D200B9143C /* NewsService.swift */, ); path = Service; sourceTree = ""; }; + 1FE6DC9D29B73B61006A06B9 /* Grade */ = { + isa = PBXGroup; + children = ( + 1F50417F295B84A300A62CA1 /* GradeSearchResultView.swift */, + 1FE6DC9E29B74ACA006A06B9 /* GradeSearchResultScreen.swift */, + ); + path = Grade; + sourceTree = ""; + }; + 1FE6DCA029B76FB7006A06B9 /* Additional Views */ = { + isa = PBXGroup; + children = ( + 1F1F42B22999331F00A0D0B7 /* SearchResultBarView.swift */, + 1FE6DCA129B77012006A06B9 /* SearchResultErrorView.swift */, + 1FE6DCA329B77094006A06B9 /* SearchResultLoadingView.swift */, + ); + path = "Additional Views"; + sourceTree = ""; + }; + 1FE6DCA529B77956006A06B9 /* Lecture */ = { + isa = PBXGroup; + children = ( + 1F74DB342972B22F00B9143C /* LectureSearchResultView.swift */, + 1FE6DCA629B77978006A06B9 /* LectureSearchResultScreen.swift */, + ); + path = Lecture; + sourceTree = ""; + }; + 1FE6DCA829B78118006A06B9 /* Cafeteria */ = { + isa = PBXGroup; + children = ( + 1F50417A295B844A00A62CA1 /* CafeteriaSearchResultView.swift */, + 1FE6DCA929B78131006A06B9 /* CafeteriaSearchResultScreen.swift */, + ); + path = Cafeteria; + sourceTree = ""; + }; + 1FE6DCAD29B79180006A06B9 /* StudyRoom */ = { + isa = PBXGroup; + children = ( + 1F504192295B897600A62CA1 /* StudyRoomSearchResultView.swift */, + 1FE6DCAE29B79280006A06B9 /* StudyRoomSearchResultScreen.swift */, + ); + path = StudyRoom; + sourceTree = ""; + }; + 1FE6DCB029B79B5D006A06B9 /* Event */ = { + isa = PBXGroup; + children = ( + 1F50419A295CC25E00A62CA1 /* EventSearchResultView.swift */, + 1FE6DCB129B79B6E006A06B9 /* EventSearchResultScreen.swift */, + ); + path = Event; + sourceTree = ""; + }; + 1FE6DCB329B79EC1006A06B9 /* News */ = { + isa = PBXGroup; + children = ( + 1FD025D62971BD2000E66981 /* NewsSearchResultView.swift */, + 1FE6DCBB29B7A824006A06B9 /* NewsSearchResultScreen.swift */, + ); + path = News; + sourceTree = ""; + }; + 1FE6DCB829B7A7F2006A06B9 /* Movie */ = { + isa = PBXGroup; + children = ( + 1FE6DCB629B7A2B0006A06B9 /* MovieSearchResultView.swift */, + 1FE6DCB929B7A7FE006A06B9 /* MovieSearchResultScreen.swift */, + ); + path = Movie; + sourceTree = ""; + }; + 1FE6DCBD29B7AB5E006A06B9 /* RoomFinder */ = { + isa = PBXGroup; + children = ( + 1F74DB302971E01F00B9143C /* RoomFinderSearchResultView.swift */, + 1FE6DCBE29B7AB76006A06B9 /* RoomFinderSearchResultScreen.swift */, + ); + path = RoomFinder; + sourceTree = ""; + }; + 1FE6DCC029B7ABCD006A06B9 /* Person */ = { + isa = PBXGroup; + children = ( + 1F74DB382972B92D00B9143C /* PersonSearchResultView.swift */, + 1FE6DCC129B7ADB7006A06B9 /* PersonSearchResultScreen.swift */, + ); + path = Person; + sourceTree = ""; + }; + 1FFEF086284E417E00ADD201 /* Recovered References */ = { + isa = PBXGroup; + children = ( + 256D0D4227D77A9C00F5EC38 /* MapViewModel.swift */, + ); + name = "Recovered References"; + sourceTree = ""; + }; 36108B9C27A3046B007DC62D /* LectureComponent */ = { isa = PBXGroup; children = ( @@ -1141,13 +1489,13 @@ 36108BD127A304B5007DC62D /* Cafeterias */ = { isa = PBXGroup; children = ( + 1F63A40229DECFA100844981 /* DishView.swift */, 08DFB97828666AD900E357DF /* CafeteriaWidgetView.swift */, 3654F38528517BB4008AD5DC /* CafeteriaView.swift */, 36108BD927A304B5007DC62D /* CafeteriaRowView.swift */, 36108BD227A304B5007DC62D /* MealPlanView.swift */, + 1F2615A129DA02E10062B8E5 /* MealPlanScreen.swift */, 36108BD627A304B5007DC62D /* MenuView.swift */, - 1F69CE45297EC99D005032CE /* DishView.swift */, - 1FACF3FC2996E34200A0B8AC /* MealPlanScreen.swift */, ); path = Cafeterias; sourceTree = ""; @@ -1171,10 +1519,11 @@ 36108BF127A30516007DC62D /* MoviesComponent */ = { isa = PBXGroup; children = ( - 1F69CE3A297DCC04005032CE /* Screen */, - 1F69CE39297DCBFE005032CE /* Service */, + 1F0F396D2A058B3500E55FCB /* Model */, + 1F63A3FB29DEC6E300844981 /* Screen */, 36108BF227A30516007DC62D /* ViewModel */, 36108BF527A30516007DC62D /* Views */, + 1F74DB3C2973039200B9143C /* Service */, ); path = MoviesComponent; sourceTree = ""; @@ -1182,8 +1531,7 @@ 36108BF227A30516007DC62D /* ViewModel */ = { isa = PBXGroup; children = ( - 36108BF327A30516007DC62D /* Movie.swift */, - 36108BF427A30516007DC62D /* MoviesViewModel.swift */, + 36108BF427A30516007DC62D /* MovieViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -1195,7 +1543,7 @@ 36108BF727A30516007DC62D /* MovieDetailedView.swift */, 36108BF827A30516007DC62D /* MovieDetailCellView.swift */, 2F1B2B8428652FC90023BD9A /* MovieDetailsBasicInfoView.swift */, - 36108BF927A30516007DC62D /* MoviesView.swift */, + 36108BF927A30516007DC62D /* MovieView.swift */, 2F1B2B86286530120023BD9A /* MovieDetailsBasicInfoRowView.swift */, 2FCF38AC286C9B5600F10915 /* MovieDetailsDetailedInfoView.swift */, 2FCF38B0286C9B9200F10915 /* MovieDetailsDetailedInfoRowView.swift */, @@ -1246,10 +1594,11 @@ 36108C0B27A307F9007DC62D /* Model */ = { isa = PBXGroup; children = ( + 1ED0F9DA2A65353000A27A75 /* AverageGrade.swift */, 36108C0C27A307F9007DC62D /* Grade.swift */, + 1F0F39692A058AAA00E55FCB /* Grade+PreviewData.swift */, 36108C0D27A307F9007DC62D /* Modus.swift */, 97F8A79227E641570099EE83 /* AcademicDegree.swift */, - 99EE85352A09216600973916 /* AverageGrade.swift */, ); path = Model; sourceTree = ""; @@ -1257,8 +1606,8 @@ 36108C0E27A307F9007DC62D /* Service */ = { isa = PBXGroup; children = ( + 1ED0F9D82A65351C00A27A75 /* AverageGradesService.swift */, 36108C0F27A307F9007DC62D /* GradesService.swift */, - 99EE85332A0920AE00973916 /* AverageGradesService.swift */, ); path = Service; sourceTree = ""; @@ -1266,13 +1615,13 @@ 36108C1027A307F9007DC62D /* Views */ = { isa = PBXGroup; children = ( + 1ED0F9D62A6534DE00A27A75 /* GradesSemesterView.swift */, + 1ED0F9D42A6534D400A27A75 /* GradesStudyProgramView.swift */, 36108C1127A307F9007DC62D /* GradesView.swift */, 36108C1227A307F9007DC62D /* GradeView.swift */, 36108C1327A307F9007DC62D /* BarChartView.swift */, 974D5B9927E5E9CB00FD7B11 /* GlowBorder.swift */, 08573BA6287B6152006AC06F /* GradeWidgetView.swift */, - 9926FD422A1D0BC000EE3ECB /* GradesStudyProgramView.swift */, - 9926FD442A1D0DBC00EE3ECB /* GradesSemesterView.swift */, ); path = Views; sourceTree = ""; @@ -1280,11 +1629,11 @@ 3616C4C82790201B000A1BC9 /* TUMSexyComponent */ = { isa = PBXGroup; children = ( + 1F63A3CC29DDE68D00844981 /* Model */, + 1F63A3CE29DDE68E00844981 /* Screen */, + 1F63A3D029DDE68E00844981 /* Service */, 3616C4CB27902086000A1BC9 /* ViewModel */, 3616C4CA27902075000A1BC9 /* Views */, - 1F69CE49297EDEB5005032CE /* Screen */, - 1F69CE4C297EDECA005032CE /* Service */, - 1F69CE4F297EDF32005032CE /* Model */, ); path = TUMSexyComponent; sourceTree = ""; @@ -1308,11 +1657,11 @@ 3616C4D727904BA7000A1BC9 /* NewsComponent */ = { isa = PBXGroup; children = ( - 1F69CE33297DB729005032CE /* Service */, + 1F63A3F829DEC6A400844981 /* Screen */, + 1FD025D52971AB8C00E66981 /* Service */, 3629BA2A27A1CEAD0036AC80 /* Views */, 36BBE7302798AFCC0018FD3F /* Model */, 3616C4D827904BB5000A1BC9 /* ViewModel */, - 1F69CE38297DCA2B005032CE /* NewsScreen */, ); path = NewsComponent; sourceTree = ""; @@ -1328,7 +1677,7 @@ 36203E872761C6EC00C24658 /* LoginComponent */ = { isa = PBXGroup; children = ( - 1F04F188297C85030085F273 /* Model */, + 1F63A3DD29DDE91100844981 /* Model */, 36E9649D277492150055777F /* Service */, 36E9649C277491F10055777F /* ViewModel */, 36E9649B277491E90055777F /* Views */, @@ -1362,8 +1711,11 @@ 36108BCB27A304B5007DC62D /* MealPlan.swift */, 36108BCC27A304B5007DC62D /* Dish.swift */, 36108BCD27A304B5007DC62D /* Cafeteria.swift */, + 1F0F39722A05913F00E55FCB /* Cafeteria+PreviewData.swift */, 36108BCE27A304B5007DC62D /* MensaMenu.swift */, + 36108BD427A304B5007DC62D /* Menu.swift */, 36108BCF27A304B5007DC62D /* DishLabel.swift */, + 1F26159929DA00EB0062B8E5 /* MensaCategory.swift */, ); path = Cafeterias; sourceTree = ""; @@ -1371,9 +1723,10 @@ 3654F35A28516855008AD5DC /* StudyRooms */ = { isa = PBXGroup; children = ( - 3654F35F285168D2008AD5DC /* RoomImageMapping.swift */, 3654F35D285168D2008AD5DC /* StudyRoom.swift */, + 3654F35F285168D2008AD5DC /* RoomImageMapping.swift */, 3654F35C285168D2008AD5DC /* StudyRoomApiResponse.swift */, + 1F0F39702A058B9F00E55FCB /* StudyRoomApiResponse+PreviewData.swift */, 3654F360285168D2008AD5DC /* StudyRoomAttribute.swift */, 3654F35B285168D2008AD5DC /* StudyRoomGroup.swift */, ); @@ -1383,9 +1736,9 @@ 3654F3692851710E008AD5DC /* RoomFinder */ = { isa = PBXGroup; children = ( - 1F71E81B29E4613F00379428 /* ViewNavigaTum */, - 1F71E81429E4611E00379428 /* Service */, - 1F71E7F429E4611000379428 /* Model */, + 1F71E85129E4803400379428 /* ViewNavigaTum */, + 1F71E83229E4800A00379428 /* Model */, + 1F63A3FF29DECDE200844981 /* Service */, 3654F36A2851710E008AD5DC /* ViewModel */, 3654F36D2851710E008AD5DC /* Entity */, 3654F36F2851710E008AD5DC /* Views */, @@ -1396,8 +1749,8 @@ 3654F36A2851710E008AD5DC /* ViewModel */ = { isa = PBXGroup; children = ( - 1F71E81729E4613500379428 /* NavigaTumDetailsViewModel.swift */, - 1F71E81829E4613500379428 /* NavigaTumViewModel.swift */, + 1F71E84D29E4802800379428 /* NavigaTumDetailsViewModel.swift */, + 1F71E84E29E4802800379428 /* NavigaTumViewModel.swift */, 3654F36B2851710E008AD5DC /* RoomFinderViewModel.swift */, 3654F36C2851710E008AD5DC /* RoomFinderMapViewModel.swift */, ); @@ -1430,12 +1783,11 @@ children = ( 3654F38328517260008AD5DC /* StudyRoomViewModel.swift */, 36108BD327A304B5007DC62D /* MealPlanViewModel.swift */, - 36108BD427A304B5007DC62D /* MenuViewModel.swift */, 1F4C836128300306006971C0 /* MapViewModel.swift */, 1F4C836328300D25006971C0 /* MapViewModel+State.swift */, - 1F71E82C29E464C400379428 /* CafeteriaWidgetViewModel.swift */, + 08DFB97C2867800C00E357DF /* CafeteriaWidgetViewModel.swift */, 08DFB9802867ACB600E357DF /* StudyRoomWidgetViewModel.swift */, - 1F69CE43297EC97E005032CE /* DishViewModel.swift */, + 1F2615A329DA02FC0062B8E5 /* DishViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -1443,12 +1795,12 @@ 3654F382285171F6008AD5DC /* StudyRooms */ = { isa = PBXGroup; children = ( + 1F26159F29DA021D0062B8E5 /* StudyRoomDetailsScreen.swift */, 08DFB97E2867AC9200E357DF /* StudyRoomWidgetView.swift */, 3654F38D28518B3D008AD5DC /* StudyGroupRowView.swift */, 3654F38928518640008AD5DC /* StudyRoomDetailsView.swift */, 3654F387285185A4008AD5DC /* StudyRoomGroupView.swift */, 3654F35E285168D2008AD5DC /* MapImagesHorizontalScrollingView.swift */, - 1F69CE3F297DDCD3005032CE /* StudyRoomDetailsScreen.swift */, ); path = StudyRooms; sourceTree = ""; @@ -1478,7 +1830,7 @@ 366F0E8227580CFB0091651D /* Campus-iOS */ = { isa = PBXGroup; children = ( - 9970686E298569CE0028D235 /* Crashlytics */, + 1F26159629D9E9010062B8E5 /* Crashlytics */, 085DE9C428AB7C3D0045095F /* AnalyticsComponent */, 3654F3692851710E008AD5DC /* RoomFinder */, 36BB6F8027B39B2200F224AB /* LectureSearchComponent */, @@ -1506,11 +1858,13 @@ 36203E7D2761B9D000C24658 /* Localizable.strings */, 366F0E8727580CFD0091651D /* Assets.xcassets */, 41E2B8232864D192005B5E72 /* Icons */, + 1F0E8EB9293E22D4006E8BB9 /* DataModels */, 1F542452285CA081008363BC /* VideoAssets */, 366F0E8C27580CFD0091651D /* Persistence.swift */, 36AD5CFD27BA064E00DAE143 /* GoogleService-Info.plist */, 366F0E8E27580CFD0091651D /* Campus_iOS.xcdatamodeld */, 366F0E8927580CFD0091651D /* Preview Content */, + 1F504173295B82FE00A62CA1 /* SearchComponent */, ); path = "Campus-iOS"; sourceTree = ""; @@ -1574,11 +1928,11 @@ 36AF61BA27A2FD7700FEBD98 /* Networking */ = { isa = PBXGroup; children = ( - 1F189EA529968E150056BBD8 /* Protocols */, - 1F189E8B29968CD70056BBD8 /* APIs */, - 1F189E9829968D620056BBD8 /* APIErrors */, + 1F26157029D9E8DC0062B8E5 /* APIErrors */, + 1F26157729D9E8DC0062B8E5 /* APIs */, + 1F26156629D9E8DC0062B8E5 /* Old APIs */, + 1F26155D29D9E8BD0062B8E5 /* Protocols */, 36AF61C227A2FD7700FEBD98 /* Cache.swift */, - 1F04F16F297AA1E00085F273 /* Old APIs */, ); path = Networking; sourceTree = ""; @@ -1630,11 +1984,11 @@ 36BB6F5527AFCD7B00F224AB /* PersonDetailedComponent */ = { isa = PBXGroup; children = ( - 36BB6F5727AFCD9300F224AB /* ViewModel */, - 1F04F187297C6BC40085F273 /* Screen */, + 1F63A3F329DDFAA100844981 /* Service */, + 1F63A3EA29DDF97E00844981 /* Screen */, 36BB6F5827AFCD9C00F224AB /* View */, + 36BB6F5727AFCD9300F224AB /* ViewModel */, 36BB6F5627AFCD8D00F224AB /* Entity */, - 1F04F186297C3FA20085F273 /* Service */, ); path = PersonDetailedComponent; sourceTree = ""; @@ -1670,11 +2024,11 @@ 36BB6F5927AFCDF900F224AB /* PersonSearchComponent */ = { isa = PBXGroup; children = ( + 1F63A3E729DDF50C00844981 /* Screen */, + 1F63A3E429DDF50600844981 /* Service */, 36BB6F5A27AFCDF900F224AB /* ViewModel */, 36BB6F5C27AFCDFA00F224AB /* Entity */, - 1F04F181297C33860085F273 /* Screen */, 36BB6F5E27AFCDFA00F224AB /* View */, - 1F04F180297BDF250085F273 /* Service */, ); path = PersonSearchComponent; sourceTree = ""; @@ -1698,7 +2052,7 @@ 36BB6F5E27AFCDFA00F224AB /* View */ = { isa = PBXGroup; children = ( - 36AD5CF727B96AD200DAE143 /* PersonSearchView.swift */, + 36BB6F5F27AFCDFA00F224AB /* PersonSearchView.swift */, ); path = View; sourceTree = ""; @@ -1708,7 +2062,6 @@ children = ( 100803452764E2C50013ED0E /* ProfileToolbar.swift */, 100803472764E37A0013ED0E /* ProfileView.swift */, - 36AD5CFB27B974F100DAE143 /* TuitionScreen.swift */, ); path = View; sourceTree = ""; @@ -1717,7 +2070,6 @@ isa = PBXGroup; children = ( 36BB6F6F27B1197400F224AB /* Profile.swift */, - 36BB6F7427B1D87200F224AB /* Tuition.swift */, ); path = Entity; sourceTree = ""; @@ -1733,6 +2085,7 @@ 36BB6F7627B26DCA00F224AB /* TuitionComponent */ = { isa = PBXGroup; children = ( + 1F59CA0729E34B8B00EA3BA1 /* Model */, 36BB6F7727B26DD300F224AB /* View */, ); path = TuitionComponent; @@ -1741,6 +2094,7 @@ 36BB6F7727B26DD300F224AB /* View */ = { isa = PBXGroup; children = ( + 1F63A3F629DEC67E00844981 /* TuitionScreen.swift */, 36BB6F7827B26DE300F224AB /* TuitionView.swift */, 36BB6F7A27B27D0D00F224AB /* TuitionCard.swift */, 08DFB97228664BC400E357DF /* TuitionWidgetView.swift */, @@ -1752,10 +2106,10 @@ 36BB6F8027B39B2200F224AB /* LectureSearchComponent */ = { isa = PBXGroup; children = ( + 1F63A3F029DDF99B00844981 /* Screen */, + 1F63A3ED29DDF99600844981 /* Service */, 36BB6F8427B39C3D00F224AB /* ViewModel */, - 1F04F17B297B0BC30085F273 /* Screen */, 36BB6F8127B39B3400F224AB /* View */, - 1F04F17A297AED190085F273 /* Service */, ); path = LectureSearchComponent; sourceTree = ""; @@ -1763,7 +2117,7 @@ 36BB6F8127B39B3400F224AB /* View */ = { isa = PBXGroup; children = ( - 36AD5CF927B9711B00DAE143 /* LectureSearchView.swift */, + 36BB6F8227B39B4300F224AB /* LectureSearchView.swift */, ); path = View; sourceTree = ""; @@ -1776,15 +2130,6 @@ path = ViewModel; sourceTree = ""; }; - 36BB6F8B27B3D58700F224AB /* Model */ = { - isa = PBXGroup; - children = ( - 1F04F172297AD41B0085F273 /* CalendarEvent.swift */, - 36AD5CF127B7FEAD00DAE143 /* TumCalendarStyle.swift */, - ); - path = Model; - sourceTree = ""; - }; 36BBE72D27989F6E0018FD3F /* HelperViews */ = { isa = PBXGroup; children = ( @@ -1799,6 +2144,7 @@ isa = PBXGroup; children = ( 36BBE7312798AFE10018FD3F /* News.swift */, + 1F0F396B2A058AEA00E55FCB /* News+PreviewData.swift */, 36BBE7332798B04D0018FD3F /* NewsSource.swift */, ); path = Model; @@ -1840,11 +2186,11 @@ 36E9649E277492AE0055777F /* CalendarComponent */ = { isa = PBXGroup; children = ( - 36BB6F8B27B3D58700F224AB /* Model */, + 1F63A3D729DDE6D500844981 /* Model */, + 1F63A3D529DDE6D500844981 /* Screen */, 36E964A0277492C00055777F /* ViewModel */, 36E9649F277492B70055777F /* Views */, - 1F04F177297AD4350085F273 /* Service */, - 1F04F176297AD42E0085F273 /* Screen */, + 1F50419E295D862100A62CA1 /* Service */, ); path = CalendarComponent; sourceTree = ""; @@ -1900,14 +2246,6 @@ path = Icons; sourceTree = ""; }; - 9970686E298569CE0028D235 /* Crashlytics */ = { - isa = PBXGroup; - children = ( - 9970686F298569E10028D235 /* CrashlyticsService.swift */, - ); - path = Crashlytics; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -2080,283 +2418,337 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 36BB6F6227AFCDFA00F224AB /* PersonSearchScreen.swift in Sources */, + 36BB6F6227AFCDFA00F224AB /* PersonSearchView.swift in Sources */, 97270F5A27AB2A4900BB25E4 /* Array+Rearrange.swift in Sources */, - 1F71E82629E4613F00379428 /* NavigaTumView.swift in Sources */, 3654F38428517260008AD5DC /* StudyRoomViewModel.swift in Sources */, + 1F504180295B84A300A62CA1 /* GradeSearchResultView.swift in Sources */, 2F1B2B87286530120023BD9A /* MovieDetailsBasicInfoRowView.swift in Sources */, 36BB6F6627AFD12B00F224AB /* PersonDetails.swift in Sources */, 36108BFD27A30517007DC62D /* MovieDetailedView.swift in Sources */, 3683C3182758117900082930 /* Model.swift in Sources */, - 1F04F179297AED150085F273 /* LectureSearchService.swift in Sources */, + 1F63A3F729DEC67E00844981 /* TuitionScreen.swift in Sources */, + 1F71E85F29E4833500379428 /* NavigaTUMAPI.swift in Sources */, + 1F63A3DC29DDE6D500844981 /* TumCalendarStyle.swift in Sources */, 08FAFD24288DF553006A0E27 /* WidgetRecommendation.swift in Sources */, 08DFB96F286647E900E357DF /* WidgetScreen.swift in Sources */, 974D5B9A27E5E9CB00FD7B11 /* GlowBorder.swift in Sources */, 08DFB97328664BC400E357DF /* TuitionWidgetView.swift in Sources */, 36108BEB27A304B6007DC62D /* CafeteriaRowView.swift in Sources */, - 1F71E80B29E4611000379428 /* NavigaTumSearchResponseSection.swift in Sources */, 08DFB9772866506900E357DF /* WidgetFrameView.swift in Sources */, + 1F6BD391296F53E900CDD625 /* DataTypeClassifierV4German.mlmodel in Sources */, 366F0E8D27580CFD0091651D /* Persistence.swift in Sources */, 36108BE327A304B5007DC62D /* DishLabel.swift in Sources */, - 1F71E80929E4611000379428 /* NavigaTumNavigationDetails.swift in Sources */, + 1F63A3E629DDF50600844981 /* PersonSearchService.swift in Sources */, 085DE9C628AB7C530045095F /* AnalyticsController.swift in Sources */, - 1F71E82429E4613F00379428 /* NavigaTumMapView.swift in Sources */, 08441F2B2874E2D00033F5B1 /* WidgetLoadingView.swift in Sources */, 36108BB927A3046B007DC62D /* LecturesViewModel+State.swift in Sources */, 36AF61EE27A2FD7800FEBD98 /* GroupBoxLabelView.swift in Sources */, - 1F69CE3C297DCC12005032CE /* MoviesScreen.swift in Sources */, - 1F69CE48297EDEA3005032CE /* TUMSexyScreen.swift in Sources */, - 36108BFB27A30517007DC62D /* MoviesViewModel.swift in Sources */, + 36108BFB27A30517007DC62D /* MovieViewModel.swift in Sources */, + 1F26158129D9E8DD0062B8E5 /* CampusOnlineAPIOld.swift in Sources */, + 1F26158229D9E8DD0062B8E5 /* TUMCabeAPIOld.swift in Sources */, 36E964AC277499860055777F /* CalendarDisplayView.swift in Sources */, + 1ED0F9D92A65351C00A27A75 /* AverageGradesService.swift in Sources */, 1F4C926F2882FD85003DC7D7 /* RoundedCorners.swift in Sources */, - 1F71E81A29E4613500379428 /* NavigaTumViewModel.swift in Sources */, - 1F71E80C29E4611000379428 /* NavigaTumNavigationEntity.swift in Sources */, + 1F504178295B83FC00A62CA1 /* SearchResultViewModel.swift in Sources */, 36FAE365277472EF00628799 /* LoginViewModel.swift in Sources */, 36108BE927A304B5007DC62D /* MenuView.swift in Sources */, 36108C1D27A307FA007DC62D /* GradeView.swift in Sources */, - 36AF61DE27A2FD7800FEBD98 /* APIResponse.swift in Sources */, - 1F71E81629E4611E00379428 /* RoomFinderService.swift in Sources */, - 1F69CE4E297EDF12005032CE /* TUMSexyLink.swift in Sources */, 3654F37A2851710E008AD5DC /* RoomFinderDetailsBaseView.swift in Sources */, 3654F388285185A4008AD5DC /* StudyRoomGroupView.swift in Sources */, + 1F71E84629E4800A00379428 /* NavigaTumOverlayMap.swift in Sources */, 0815249428E445030098A2C3 /* Date+Time.swift in Sources */, 36BB6F7027B1197400F224AB /* Profile.swift in Sources */, + 1F504190295B85AB00A62CA1 /* String+Keep.swift in Sources */, 08D0703A28776DD6004140B1 /* TextWidgetView.swift in Sources */, 08FAFD15287DC484006A0E27 /* CalendarWidgetView.swift in Sources */, 3654F3762851710E008AD5DC /* RoomFinderViewModel.swift in Sources */, + 1F26158429D9E8DD0062B8E5 /* TUMOnlineAPIOld.swift in Sources */, 36108BC427A3046B007DC62D /* LectureDetailsDetailedInfoRowView.swift in Sources */, + 1F0F39732A05913F00E55FCB /* Cafeteria+PreviewData.swift in Sources */, + 1FD025D22971A31300E66981 /* NewsSearchResultViewModel.swift in Sources */, + 1F26158729D9E8DD0062B8E5 /* TUMDevAppAPIError.swift in Sources */, 36108C1C27A307FA007DC62D /* GradesView.swift in Sources */, + 1F71E84B29E4800A00379428 /* NavigaTumNavigationProperty.swift in Sources */, + 1F63A3D329DDE68E00844981 /* TUMSexyScreen.swift in Sources */, + 1FE6DCAC29B790F8006A06B9 /* SearchError.swift in Sources */, 3654F365285168D2008AD5DC /* RoomImageMapping.swift in Sources */, - 1FFF9AC6297D31830098E874 /* ProfileService.swift in Sources */, 2FCF38B1286C9B9200F10915 /* MovieDetailsDetailedInfoRowView.swift in Sources */, - 36AD5CFC27B974F100DAE143 /* TuitionScreen.swift in Sources */, 36108BE227A304B5007DC62D /* MensaMenu.swift in Sources */, 36108BC027A3046B007DC62D /* LecturesView.swift in Sources */, + 1ED0F9DB2A65353000A27A75 /* AverageGrade.swift in Sources */, 0805DB7928C933AE00712FF2 /* Operators.swift in Sources */, + 1F504185295B84DC00A62CA1 /* ComparisonToken.swift in Sources */, + 1F0E8EBB293E22FF006E8BB9 /* DataTypeClassifierV2.mlmodel in Sources */, + 1F26156529D9E8BD0062B8E5 /* API.swift in Sources */, 97F8A79327E641570099EE83 /* AcademicDegree.swift in Sources */, - 36AF61D927A2FD7800FEBD98 /* TUMSexyAPIOld.swift in Sources */, - 1F189E9329968D260056BBD8 /* TUMDevAppAPI.swift in Sources */, + 1FD025D72971BD2000E66981 /* NewsSearchResultView.swift in Sources */, 3654F38E28518B3D008AD5DC /* StudyGroupRowView.swift in Sources */, 3654F358285167C3008AD5DC /* StudyRoomsService.swift in Sources */, - 36AD5CF227B7FEAD00DAE143 /* TumCalendarStyle.swift in Sources */, - 1FA538EE297560CD004C70A8 /* MainAPI.swift in Sources */, 36BB6F7F27B386D100F224AB /* AddToContactsView.swift in Sources */, - 1F189EA729968E5C0056BBD8 /* API.swift in Sources */, - 36AF61DF27A2FD7800FEBD98 /* TUMOnlineAPIOld.swift in Sources */, + 1F26158F29D9E8DD0062B8E5 /* TUMDevAppAPI.swift in Sources */, + 1F71E84229E4800A00379428 /* NavigaTumNavigationCoordinates.swift in Sources */, 3654F37B2851710E008AD5DC /* RoomFinderListView.swift in Sources */, 36C70FB32854D2AB0097416E /* PanelContentStudyGroupsListView.swift in Sources */, + 1FE6DCA729B77978006A06B9 /* LectureSearchResultScreen.swift in Sources */, + 1F26159129D9E8DD0062B8E5 /* TUMOnlineAPI.swift in Sources */, 36C70FB128538A190097416E /* PanelContentCafeteriasListView.swift in Sources */, - 36108BE727A304B5007DC62D /* MenuViewModel.swift in Sources */, - 36AF61E127A2FD7800FEBD98 /* TUMCabeAPIOld.swift in Sources */, + 1F63A3F529DDFAA100844981 /* PersonDetailedService.swift in Sources */, + 36108BE727A304B5007DC62D /* Menu.swift in Sources */, 3654F37D2851710E008AD5DC /* RoomFinderDetailsMapImagesView.swift in Sources */, 36108BB627A3046B007DC62D /* LectureDetailsViewModel+State.swift in Sources */, - 1FACF3F92996A49300A0B8AC /* TUMDevAppAPIOld.swift in Sources */, - 99EE85362A09216600973916 /* AverageGrade.swift in Sources */, 36108BBF27A3046B007DC62D /* LectureDetailsService.swift in Sources */, 0805E72C28CC2278003C5CFD /* HashFunction.swift in Sources */, 36982BD827A2739000515847 /* Collapsible.swift in Sources */, 0815249E28E4A6310098A2C3 /* Date+daysBetween.swift in Sources */, 1F2068DE28FD731200DBDF67 /* LoginViewModel+TokenState.swift in Sources */, - 36AD5CFA27B9711B00DAE143 /* LectureSearchView.swift in Sources */, - 1F189E9529968D330056BBD8 /* TUMSexyAPI.swift in Sources */, 1F4C836228300306006971C0 /* MapViewModel.swift in Sources */, - 1F189E9729968D490056BBD8 /* MVGAPI.swift in Sources */, - 36108BFF27A30517007DC62D /* MoviesView.swift in Sources */, - 1F71E80729E4611000379428 /* NavigaTumNavigationAdditionalProperties.swift in Sources */, - 1F189E9129968D130056BBD8 /* EatAPI.swift in Sources */, + 36108BFF27A30517007DC62D /* MovieView.swift in Sources */, + 1F63A3EF29DDF99600844981 /* LectureSearchService.swift in Sources */, + 1F504199295CC24E00A62CA1 /* EventSearchResultViewModel.swift in Sources */, + 1F74DB2D2971D0D200B9143C /* NewsService.swift in Sources */, 36AF61E927A2FD7800FEBD98 /* ErrorHandler.swift in Sources */, 36BB6F6027AFCDFA00F224AB /* PersonSearchViewModel.swift in Sources */, 36982BD627A251A700515847 /* NewsCardsHorizontalScrollingView.swift in Sources */, + 1F50419B295CC25E00A62CA1 /* EventSearchResultView.swift in Sources */, 36AF61E727A2FD7800FEBD98 /* ErrorEmittingViewModifier.swift in Sources */, - 1F71E80629E4611000379428 /* NavigaTumNavigationMaps.swift in Sources */, 3654F362285168D2008AD5DC /* StudyRoomApiResponse.swift in Sources */, + 1F63A3D229DDE68E00844981 /* TUMSexyLink.swift in Sources */, + 1F0F396A2A058AAA00E55FCB /* Grade+PreviewData.swift in Sources */, 36108BC527A3046B007DC62D /* LectureDetailsDetailedInfoView.swift in Sources */, - 1F04F18A297C85120085F273 /* Token.swift in Sources */, + 1F50418C295B854900A62CA1 /* GlobalSearch.swift in Sources */, 3654F363285168D2008AD5DC /* StudyRoom.swift in Sources */, 08573BA5287847DC006AC06F /* MapLocation.swift in Sources */, + 1FE6DCA429B77094006A06B9 /* SearchResultLoadingView.swift in Sources */, + 1F71E85A29E4803400379428 /* NavigaTumMapView.swift in Sources */, 3629BA3127A1D0AD0036AC80 /* ScrollableCardsViewModifier.swift in Sources */, 36108C1927A307FA007DC62D /* Grade.swift in Sources */, 36108BBA27A3046B007DC62D /* LectureDetailsScreen.swift in Sources */, - 36203E8D2761C6EC00C24658 /* Credentials.swift in Sources */, 08573BA7287B6152006AC06F /* GradeWidgetView.swift in Sources */, + 1F71E84429E4800A00379428 /* NavigaTumNavigationMaps.swift in Sources */, + 1ED0F9D52A6534D400A27A75 /* GradesStudyProgramView.swift in Sources */, + 1F63A3F229DDF99B00844981 /* LectureSearchScreen.swift in Sources */, + 1F63A3FA29DEC6A400844981 /* NewsScreen.swift in Sources */, 0805E72228CA9DF5003C5CFD /* AppUsageData.swift in Sources */, + 1FAC1A722A0644B600A0A4F3 /* ExpandIcon.swift in Sources */, + 1F26156429D9E8BD0062B8E5 /* MainAPI.swift in Sources */, + 1F2615A429DA02FC0062B8E5 /* DishViewModel.swift in Sources */, + 1F26157F29D9E8DD0062B8E5 /* EatAPIOld.swift in Sources */, 08FAFD1E288DEDD3006A0E27 /* WidgetRecommenderStrategy.swift in Sources */, 3654F3792851710E008AD5DC /* RoomFinderDetailsMapView.swift in Sources */, + 1F26158D29D9E8DD0062B8E5 /* TUMSexyAPI.swift in Sources */, 3698CBED2761E014001C5735 /* CustomRoundedBorderTextFieldStyle.swift in Sources */, 36108BE627A304B5007DC62D /* MealPlanViewModel.swift in Sources */, + 1F2615A229DA02E10062B8E5 /* MealPlanScreen.swift in Sources */, 36203E8B2761C6EC00C24658 /* TUMSplashScreen.swift in Sources */, - 36AF61DA27A2FD7800FEBD98 /* EatAPIOld.swift in Sources */, + 1F26159A29DA00EB0062B8E5 /* MensaCategory.swift in Sources */, 36108BEF27A304B6007DC62D /* MapContentView.swift in Sources */, - 1F71E80E29E4611000379428 /* NavigaTumRoomFinderMap.swift in Sources */, - 1F71E80A29E4611000379428 /* NavigaTumSearchResponse.swift in Sources */, + 1FE6DCA229B77012006A06B9 /* SearchResultErrorView.swift in Sources */, 3616C4CF279020D3000A1BC9 /* TUMSexyViewModel.swift in Sources */, - 1F69CE44297EC97E005032CE /* DishViewModel.swift in Sources */, + 1F50418E295B857B00A62CA1 /* String+Levenshtein.swift in Sources */, 36AF61EC27A2FD7800FEBD98 /* Error+Category.swift in Sources */, - 1F71E82229E4613F00379428 /* NavigaTumDetailsView.swift in Sources */, - 36AF61DD27A2FD7800FEBD98 /* CampusOnlineAPIOld.swift in Sources */, + 1F74DB3B2973038800B9143C /* MovieService.swift in Sources */, + 1F26158C29D9E8DD0062B8E5 /* TUMSexyAPIError.swift in Sources */, 36AD5CF427B8C83500DAE143 /* CalendarSingleEventView.swift in Sources */, 36108BBC27A3046B007DC62D /* LectureDetails.swift in Sources */, + 1FE6DCAA29B78131006A06B9 /* CafeteriaSearchResultScreen.swift in Sources */, 36108BC627A3046B007DC62D /* LectureDetailsBasicInfoRowView.swift in Sources */, - 1F189E9E29968D9B0056BBD8 /* EatAPIError.swift in Sources */, + 1F71E85D29E4803400379428 /* NavigaTumListView.swift in Sources */, 08FAFD20288DEE3B006A0E27 /* Widget.swift in Sources */, 36108BDF27A304B5007DC62D /* MealPlan.swift in Sources */, 36BBE7342798B04D0018FD3F /* NewsSource.swift in Sources */, + 1F50417B295B844A00A62CA1 /* CafeteriaSearchResultView.swift in Sources */, + 1F50417E295B848000A62CA1 /* CafeteriaSearchResultViewModel.swift in Sources */, 3654F38628517BB4008AD5DC /* CafeteriaView.swift in Sources */, 36BB6F8D27B3F25A00F224AB /* NSMutableString+Extensions.swift in Sources */, - 1F04F175297AD4280085F273 /* CalendarScreen.swift in Sources */, + 1F50418A295B852F00A62CA1 /* GradeSearchResultViewModel.swift in Sources */, 36AF61E827A2FD7800FEBD98 /* AlertErrorHandler.swift in Sources */, 36BB6F5327AFCCB500F224AB /* PersonDetailedView.swift in Sources */, + 1F63A3EC29DDF97E00844981 /* PersonDetailedScreen.swift in Sources */, 3654F37E2851710E008AD5DC /* RoomFinderDetailsView.swift in Sources */, 36108BC327A3046B007DC62D /* LectureDetailsTitleView.swift in Sources */, 3654F364285168D2008AD5DC /* MapImagesHorizontalScrollingView.swift in Sources */, 36E964A5277493D90055777F /* CalendarViewModel.swift in Sources */, 0805E72428CAABB3003C5CFD /* AnalyticsError.swift in Sources */, - 1F71E82F29E4667C00379428 /* NavigaTUMAPI.swift in Sources */, + 1ED0F9D72A6534DE00A27A75 /* GradesSemesterView.swift in Sources */, + 1F2615A029DA021D0062B8E5 /* StudyRoomDetailsScreen.swift in Sources */, + 1F26159E29DA015D0062B8E5 /* MealPlanService.swift in Sources */, 3654F361285168D2008AD5DC /* StudyRoomGroup.swift in Sources */, 36BB6F7927B26DE300F224AB /* TuitionView.swift in Sources */, 3654F3782851710E008AD5DC /* FoundRoom.swift in Sources */, 36BB6F6127AFCDFA00F224AB /* Person.swift in Sources */, 0805DB7728C7F3E600712FF2 /* AnalyticsOptInView.swift in Sources */, + 1F26156229D9E8BD0062B8E5 /* APIError.swift in Sources */, 1FAF9F0C284D2ABC000ABE93 /* MapScreenView.swift in Sources */, 36108C1527A307F9007DC62D /* GradesViewModel.swift in Sources */, - 1F69CE35297DB732005032CE /* NewsService.swift in Sources */, + 1F71E85929E4803400379428 /* NavigaTumDetailsBaseView.swift in Sources */, 1F2068E428FD73CB00DBDF67 /* TokenPermissionsViewModel+PermissionType.swift in Sources */, 36108BE127A304B5007DC62D /* Cafeteria.swift in Sources */, - 1F04F183297C3EF70085F273 /* PersonDetailedScreen.swift in Sources */, 36108BE027A304B5007DC62D /* Dish.swift in Sources */, - 1F71E80429E4611000379428 /* NavigaTumNavigationCoordinates.swift in Sources */, 36108BBB27A3046B007DC62D /* LecturesScreen.swift in Sources */, 36BB6F6427AFCFFB00F224AB /* PersonDetailedViewModel.swift in Sources */, 36AF61E427A2FD7800FEBD98 /* NetworkingError.swift in Sources */, - 1FACF3FB2996A65700A0B8AC /* MealPlanService.swift in Sources */, - 36BB6F8327B39B4300F224AB /* LectureSearchScreen.swift in Sources */, + 36BB6F8327B39B4300F224AB /* LectureSearchView.swift in Sources */, 36108BFA27A30517007DC62D /* Movie.swift in Sources */, 36108BB727A3046B007DC62D /* LectureDetailsViewModel.swift in Sources */, + 1F63A3E929DDF50C00844981 /* PersonSearchScreen.swift in Sources */, + 1F71E85C29E4803400379428 /* NavigaTumView.swift in Sources */, + 1F488336296EF0CD0002CE77 /* DataTypeClassifierV3.mlmodel in Sources */, + 1F74DB352972B22F00B9143C /* LectureSearchResultView.swift in Sources */, + 1FE6DCBC29B7A824006A06B9 /* NewsSearchResultScreen.swift in Sources */, 36AF61E027A2FD7800FEBD98 /* Cache.swift in Sources */, - 1F69CE37297DCA22005032CE /* NewsScreen.swift in Sources */, 36AF61F027A2FD7800FEBD98 /* DecoderProtocol.swift in Sources */, - 1F189E9A29968D790056BBD8 /* TUMOnlineAPIError.swift in Sources */, - 1F69CE40297DDCD3005032CE /* StudyRoomDetailsScreen.swift in Sources */, 08FAFD1C288DEDBF006A0E27 /* TimeStrategy.swift in Sources */, - 1F04F171297AA5F40085F273 /* Service.swift in Sources */, + 1F63A3E129DDE91100844981 /* Confirmation.swift in Sources */, 08FAFD17288474FC006A0E27 /* WidgetMapBackgroundView.swift in Sources */, 3698CBEF2761E6CC001C5735 /* TokenConfirmationView.swift in Sources */, 0805E72828CC0954003C5CFD /* AppUsageDataEntity.swift in Sources */, 36BB6F7527B1D87200F224AB /* Tuition.swift in Sources */, + 1FE6DCC229B7ADB7006A06B9 /* PersonSearchResultScreen.swift in Sources */, 3616C4CD279020A0000A1BC9 /* TUMSexyView.swift in Sources */, 1F4C836728300E79006971C0 /* CafeteriasService.swift in Sources */, 36108C1E27A307FA007DC62D /* BarChartView.swift in Sources */, + 1F71E85B29E4803400379428 /* NavigaTumMapImagesView.swift in Sources */, 3683C31A2758118A00082930 /* MockModel.swift in Sources */, - 1F04F16E297A9A700085F273 /* CalendarService.swift in Sources */, - 1F04F17F297BDF1E0085F273 /* PersonSearchService.swift in Sources */, 08DFB9812867ACB600E357DF /* StudyRoomWidgetViewModel.swift in Sources */, + 1F504176295B83AA00A62CA1 /* SearchResultView.swift in Sources */, 3629BA3327A1E4A90036AC80 /* RoundedCornersShape.swift in Sources */, 1F2068E228FD73C400DBDF67 /* TokenPermissionsViewModel+State.swift in Sources */, - 1F71E82729E4613F00379428 /* NavigaTumListView.swift in Sources */, + 1F26157E29D9E8DD0062B8E5 /* TUMDevAppAPIOld.swift in Sources */, 3654F37C2851710E008AD5DC /* RoomFinderView.swift in Sources */, - 1F71E80529E4611000379428 /* NavigaTumOverlaysMaps.swift in Sources */, 36BBE72F27989F8C0018FD3F /* SFSafariViewWrapper.swift in Sources */, 08FAFD272898A2B8006A0E27 /* LocationStrategy.swift in Sources */, - 1F183A172979D19000B5D22D /* APIError.swift in Sources */, + 1F63A3DB29DDE6D500844981 /* CalendarEvent.swift in Sources */, 1F2068DC28FD6E2800DBDF67 /* LoginViewModel+LoginState.swift in Sources */, + 1F26158E29D9E8DD0062B8E5 /* EatAPI.swift in Sources */, + 1F71E84929E4800A00379428 /* NavigaTumSearchResponseSection.swift in Sources */, + 1F63A3FD29DEC6E300844981 /* MovieScreen.swift in Sources */, 36AF61E627A2FD7800FEBD98 /* View+Error.swift in Sources */, + 1F26156329D9E8BD0062B8E5 /* Service.swift in Sources */, + 1F26158629D9E8DD0062B8E5 /* NetworkingAPIOld.swift in Sources */, + 1FE6DCB529B79FDF006A06B9 /* MovieSearchResultViewModel.swift in Sources */, 36BBE7322798AFE10018FD3F /* News.swift in Sources */, + 1F71E86129E4834200379428 /* NavigaTUMAPIError.swift in Sources */, + 1F26158329D9E8DD0062B8E5 /* TUMSexyAPIOld.swift in Sources */, 36108C1627A307F9007DC62D /* GradesViewModel+ChartData.swift in Sources */, - 1F71E82D29E464C400379428 /* CafeteriaWidgetViewModel.swift in Sources */, - 36AD5CF827B96AD200DAE143 /* PersonSearchView.swift in Sources */, 36108C1727A307F9007DC62D /* GradesViewModel+State.swift in Sources */, - 1F04F173297AD41B0085F273 /* CalendarEvent.swift in Sources */, - 1F189E9C29968D880056BBD8 /* TUMCabeAPIError.swift in Sources */, - 1F189E8F29968CFC0056BBD8 /* TUMCabeAPI.swift in Sources */, 08038F96287430FB0048DAE5 /* WidgetTitleView.swift in Sources */, - 9926FD432A1D0BC000EE3ECB /* GradesStudyProgramView.swift in Sources */, + 1FE6DCB229B79B6E006A06B9 /* EventSearchResultScreen.swift in Sources */, 36FF906F2773BE8100F4C785 /* AuthenticationHandler.swift in Sources */, + 1F74DB2F2971E01100B9143C /* RoomFinderSearchResultViewModel.swift in Sources */, 36108BFE27A30517007DC62D /* MovieDetailCellView.swift in Sources */, - 1F69CE46297EC99D005032CE /* DishView.swift in Sources */, - 1F69CE4B297EDEC7005032CE /* TUMSexyService.swift in Sources */, 2FCF38AD286C9B5600F10915 /* MovieDetailsDetailedInfoView.swift in Sources */, 36108BE527A304B5007DC62D /* MealPlanView.swift in Sources */, + 1FE6DCAF29B79281006A06B9 /* StudyRoomSearchResultScreen.swift in Sources */, 36AF61EF27A2FD7800FEBD98 /* LoadingView.swift in Sources */, - 1F69CE3E297DCC19005032CE /* MovieService.swift in Sources */, 36AF61EA27A2FD7800FEBD98 /* Environment+Error.swift in Sources */, 3654F38A28518640008AD5DC /* StudyRoomDetailsView.swift in Sources */, 36AF61ED27A2FD7800FEBD98 /* FailedView.swift in Sources */, + 1F71E84A29E4800A00379428 /* NavigaTumNavigationEntity.swift in Sources */, 36108BBE27A3046B007DC62D /* LecturesService.swift in Sources */, - 99706870298569E10028D235 /* CrashlyticsService.swift in Sources */, - 1F71E83129E46A1000379428 /* NavigaTUMAPIError.swift in Sources */, + 1F26158B29D9E8DD0062B8E5 /* MVGAPIError.swift in Sources */, 36108BF027A304B6007DC62D /* PanelContentView.swift in Sources */, 36AF61F127A2FD7800FEBD98 /* XMLSerializer.swift in Sources */, - 1F71E81929E4613500379428 /* NavigaTumDetailsViewModel.swift in Sources */, - 1F71E80829E4611000379428 /* NavigaTumOverlayMap.swift in Sources */, 08FAFD1A288DED6F006A0E27 /* WidgetRecommender.swift in Sources */, + 1F63A40329DECFA100844981 /* DishView.swift in Sources */, 08D9535A28E34596007ED2F1 /* Array+Groups.swift in Sources */, 36108C1A27A307FA007DC62D /* Modus.swift in Sources */, + 1F50419D295D861D00A62CA1 /* CalendarService.swift in Sources */, + 1F26158A29D9E8DD0062B8E5 /* TUMCabeAPIError.swift in Sources */, + 1F26158829D9E8DD0062B8E5 /* TUMOnlineAPIError.swift in Sources */, 3654F38028517156008AD5DC /* ImageFullScreenView.swift in Sources */, + 1F74DB312971E01F00B9143C /* RoomFinderSearchResultView.swift in Sources */, + 1F71E84829E4800A00379428 /* NavigaTumSearchResponse.swift in Sources */, 36108BC127A3046B007DC62D /* LectureView.swift in Sources */, + 1F36EC8229377CD300349FC1 /* DataTypeClassifierV1.mlmodel in Sources */, 366F0E9027580CFD0091651D /* Campus_iOS.xcdatamodeld in Sources */, 36108BFC27A30517007DC62D /* MovieCard.swift in Sources */, - 1F71E82529E4613F00379428 /* NavigaTumMapImagesView.swift in Sources */, 36BB6F6A27AFD2A100F224AB /* PhoneExtension.swift in Sources */, - 9926FD452A1D0DBC00EE3ECB /* GradesSemesterView.swift in Sources */, - 36AF61DC27A2FD7800FEBD98 /* NetworkingAPIOld.swift in Sources */, - 1F71E82329E4613F00379428 /* NavigaTumDetailsBaseView.swift in Sources */, 08DFB97528664CFC00E357DF /* TuitionDetailsView.swift in Sources */, + 1FE6DCB729B7A2B0006A06B9 /* MovieSearchResultView.swift in Sources */, + 1F71E85829E4803400379428 /* NavigaTumDetailsView.swift in Sources */, + 1F71E84329E4800A00379428 /* NavigaTumOverlaysMaps.swift in Sources */, 0815249628E45C390098A2C3 /* MLModelDataHandler.swift in Sources */, - 1F71E80D29E4611000379428 /* NavigaTumNavigationProperty.swift in Sources */, 3654F366285168D2008AD5DC /* StudyRoomAttribute.swift in Sources */, + 1F26158929D9E8DD0062B8E5 /* EatAPIError.swift in Sources */, + 1F0F396F2A058B5000E55FCB /* Movie+PreviewData.swift in Sources */, + 1F71E85029E4802800379428 /* NavigaTumViewModel.swift in Sources */, 1FBFA168285E5B2D00FC1515 /* PanelContentListView.swift in Sources */, 100803462764E2C50013ED0E /* ProfileToolbar.swift in Sources */, 36BB6F7B27B27D0D00F224AB /* TuitionCard.swift in Sources */, 36AF61E227A2FD7800FEBD98 /* Constants.swift in Sources */, + 1F74DB332972B1C600B9143C /* LectureSearchResultViewModel.swift in Sources */, 36BB6F6C27AFD2B900F224AB /* Room.swift in Sources */, + 1F504187295B84F400A62CA1 /* Searchable.swift in Sources */, 0815249828E492070098A2C3 /* RecommenderError.swift in Sources */, 36AF61EB27A2FD7800FEBD98 /* ErrorCategory.swift in Sources */, - 1F04F185297C3F990085F273 /* PersonDetailedService.swift in Sources */, - 1F04F18C297C85190085F273 /* Confirmation.swift in Sources */, - 1F69CE42297EC94E005032CE /* DishService.swift in Sources */, 36203E8C2761C6EC00C24658 /* LoginView.swift in Sources */, 1F33B2ED282B084100C898E4 /* MockGradesViewModel.swift in Sources */, + 1F26159D29DA015D0062B8E5 /* DishService.swift in Sources */, + 1FE6DCBF29B7AB76006A06B9 /* RoomFinderSearchResultScreen.swift in Sources */, 36E964A7277498540055777F /* CalendarContentView.swift in Sources */, + 1FE6DC9F29B74ACA006A06B9 /* GradeSearchResultScreen.swift in Sources */, 0815249C28E4A38D0098A2C3 /* CLLocation+isInvalid.swift in Sources */, + 1F26158529D9E8DD0062B8E5 /* APIResponse.swift in Sources */, + 1F63A3D429DDE68E00844981 /* TUMSexyService.swift in Sources */, 36AF61E327A2FD7800FEBD98 /* APIConstants.swift in Sources */, - 1F71E80329E4611000379428 /* NavigaTumRoomFinderMaps.swift in Sources */, + 1F6BD38F296F53E200CDD625 /* DataTypeClassifierV4English.mlmodel in Sources */, 3629BA2C27A1CECA0036AC80 /* NewsView.swift in Sources */, + 1F0F39752A05917400E55FCB /* CalendarEvent+PreviewData.swift in Sources */, + 1F1F42B32999331F00A0D0B7 /* SearchResultBarView.swift in Sources */, 1FB82E3428F95776007B1858 /* TokenPermissionsView.swift in Sources */, 36BB6F7327B1CD9200F224AB /* ProfileViewModel.swift in Sources */, + 1F71E84F29E4802800379428 /* NavigaTumDetailsViewModel.swift in Sources */, 36BB6F6827AFD26500F224AB /* Organization.swift in Sources */, 082F756628AD2F0E00FE0D52 /* AnalyticsStrategy.swift in Sources */, 3616C4DB27904BD3000A1BC9 /* NewsViewModel.swift in Sources */, + 1F26159529D9E8F10062B8E5 /* ProfileService.swift in Sources */, + 1F26159229D9E8DD0062B8E5 /* TUMCabeAPI.swift in Sources */, 2F1B2B8528652FC90023BD9A /* MovieDetailsBasicInfoView.swift in Sources */, 08DFB97928666AD900E357DF /* CafeteriaWidgetView.swift in Sources */, + 1F74DB372972B87800B9143C /* PersonSearchResultViewModel.swift in Sources */, 3629BA2E27A1CEFA0036AC80 /* NewsCard.swift in Sources */, + 1F26159029D9E8DD0062B8E5 /* MVGAPI.swift in Sources */, 100803482764E37A0013ED0E /* ProfileView.swift in Sources */, - 1F189EA229968DB90056BBD8 /* TUMSexyAPIError.swift in Sources */, + 1F63A40129DECE0000844981 /* RoomFinderService.swift in Sources */, + 08DFB97D2867800C00E357DF /* CafeteriaWidgetViewModel.swift in Sources */, + 1FE6DCBA29B7A7FE006A06B9 /* MovieSearchResultScreen.swift in Sources */, + 1F504172295B82F900A62CA1 /* SearchView.swift in Sources */, + 1F71E84529E4800A00379428 /* NavigaTumNavigationAdditionalProperties.swift in Sources */, 36AD5CF627B8D97500DAE143 /* LectureDetailsEventInfoView.swift in Sources */, 1F4C836428300D25006971C0 /* MapViewModel+State.swift in Sources */, + 1F26159829D9E9010062B8E5 /* CrashlyticsService.swift in Sources */, + 1F71E84129E4800A00379428 /* NavigaTumRoomFinderMaps.swift in Sources */, 08DFB97F2867AC9200E357DF /* StudyRoomWidgetView.swift in Sources */, 36108C1827A307F9007DC62D /* GradesScreen.swift in Sources */, 36108BC827A3046B007DC62D /* LecturesDetailView.swift in Sources */, 3654F38C285187C5008AD5DC /* PanelSearchBarView.swift in Sources */, + 1F71E84729E4800A00379428 /* NavigaTumNavigationDetails.swift in Sources */, + 1F71E84C29E4800A00379428 /* NavigaTumRoomFinderMap.swift in Sources */, + 1F63A3E229DDE91100844981 /* Token.swift in Sources */, + 1F0F39772A05975200E55FCB /* SearchState.swift in Sources */, + 1F63A3DA29DDE6D500844981 /* CalendarScreen.swift in Sources */, + 1F0F396C2A058AEA00E55FCB /* News+PreviewData.swift in Sources */, + 1F74DB392972B92D00B9143C /* PersonSearchResultView.swift in Sources */, + 1F504193295B897600A62CA1 /* StudyRoomSearchResultView.swift in Sources */, 1FB82E3628F96C9E007B1858 /* TokenPermissionsViewModel.swift in Sources */, - 99EE85342A0920AE00973916 /* AverageGradesService.swift in Sources */, + 1FAC1A6F2A0632F900A0A4F3 /* View+Search.swift in Sources */, 36108C1B27A307FA007DC62D /* GradesService.swift in Sources */, - 1F189E8D29968CE50056BBD8 /* TUMOnlineAPI.swift in Sources */, 36AF61E527A2FD7800FEBD98 /* BackendError.swift in Sources */, + 1F504195295B898000A62CA1 /* StudyRoomSearchResultViewModel.swift in Sources */, 3654F3772851710E008AD5DC /* RoomFinderMapViewModel.swift in Sources */, - 36AF61DB27A2FD7800FEBD98 /* MVGAPIOld.swift in Sources */, - 1FACF3FD2996E34200A0B8AC /* MealPlanScreen.swift in Sources */, + 1F0F39712A058B9F00E55FCB /* StudyRoomApiResponse+PreviewData.swift in Sources */, 366F0E8427580CFB0091651D /* App.swift in Sources */, - 1F189EA029968DA90056BBD8 /* TUMDevAppAPIError.swift in Sources */, - 1F189EA429968DE60056BBD8 /* MVGAPIError.swift in Sources */, 36FF90652773BB8200F4C785 /* Extensions.swift in Sources */, 36108C1427A307F9007DC62D /* GradeColor.swift in Sources */, 36108BBD27A3046B007DC62D /* Lecture.swift in Sources */, 36BB6F8627B39C5300F224AB /* LectureSearchViewModel.swift in Sources */, 36108BC227A3046B007DC62D /* LectureDetailsBasicInfoView.swift in Sources */, + 1F63A3E329DDE91100844981 /* Credentials.swift in Sources */, 36108BC727A3046B007DC62D /* LectureDetailsLinkView.swift in Sources */, 36108BB827A3046B007DC62D /* LecturesViewModel.swift in Sources */, 08FAFD292898B6C8006A0E27 /* SpatioTemporalStrategy.swift in Sources */, 36108C0227A30762007DC62D /* Enums.swift in Sources */, + 1F26158029D9E8DD0062B8E5 /* MVGAPIOld.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Campus-iOS.xcodeproj/xcshareddata/xcbaselines/366F0E9427580CFD0091651D.xcbaseline/3439EAFF-1FCF-41B1-96A9-2FD6666B9105.plist b/Campus-iOS.xcodeproj/xcshareddata/xcbaselines/366F0E9427580CFD0091651D.xcbaseline/3439EAFF-1FCF-41B1-96A9-2FD6666B9105.plist new file mode 100644 index 00000000..fc998b06 --- /dev/null +++ b/Campus-iOS.xcodeproj/xcshareddata/xcbaselines/366F0E9427580CFD0091651D.xcbaseline/3439EAFF-1FCF-41B1-96A9-2FD6666B9105.plist @@ -0,0 +1,22 @@ + + + + + classNames + + Campus_iOSTests + + testPerformanceExample() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.005971 + baselineIntegrationDisplayName + Local Baseline + + + + + + diff --git a/Campus-iOS.xcodeproj/xcshareddata/xcbaselines/366F0E9427580CFD0091651D.xcbaseline/Info.plist b/Campus-iOS.xcodeproj/xcshareddata/xcbaselines/366F0E9427580CFD0091651D.xcbaseline/Info.plist new file mode 100644 index 00000000..e07a8d15 --- /dev/null +++ b/Campus-iOS.xcodeproj/xcshareddata/xcbaselines/366F0E9427580CFD0091651D.xcbaseline/Info.plist @@ -0,0 +1,40 @@ + + + + + runDestinationsByUUID + + 3439EAFF-1FCF-41B1-96A9-2FD6666B9105 + + localComputer + + busSpeedInMHz + 0 + cpuCount + 1 + cpuKind + Apple M1 + cpuSpeedInMHz + 0 + logicalCPUCoresPerPackage + 8 + modelCode + MacBookAir10,1 + physicalCPUCoresPerPackage + 8 + platformIdentifier + com.apple.platform.macosx + + targetArchitecture + arm64 + targetDevice + + modelCode + iPhone15,2 + platformIdentifier + com.apple.platform.iphonesimulator + + + + + diff --git a/Campus-iOS/Base/Entity/EntityImporter.swift b/Campus-iOS/Base/Entity/EntityImporter.swift deleted file mode 100644 index 52479b9e..00000000 --- a/Campus-iOS/Base/Entity/EntityImporter.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// EntityImporter.swift -// Campus-iOS -// -// Created by Milen Vitanov on 13.01.22. -// - -import UIKit.UIApplication -//import CoreData -import Alamofire -import FirebaseCrashlytics - -protocol Entity: Decodable { - -} - -enum ImporterError: Error { - case invalidData -} - -final class Importer { - typealias RequestHandler = (Result) -> Void - - let endpoint: URLRequestConvertible - let dateDecodingStrategy: DecoderType.DateDecodingStrategy? - let predicate: NSPredicate? - - private let sessionManager = Session.defaultSession - - required init(endpoint: URLRequestConvertible, predicate: NSPredicate? = nil, dateDecodingStrategy: DecoderType.DateDecodingStrategy? = nil) { - self.endpoint = endpoint - self.predicate = predicate - self.dateDecodingStrategy = dateDecodingStrategy - } - - func performFetch(handler: RequestHandler? = nil) { - sessionManager.request(endpoint) - .validate(statusCode: 200..<300) - .validate(contentType: DecoderType.contentType) - .responseData { response in - if let responseError = response.error { - handler?(.failure(BackendError.AFError(message: responseError.localizedDescription))) - return - } - guard let data = response.data else { - handler?(.failure(ImporterError.invalidData)) - return - } - - let decoder = DecoderType.instantiate() - if let strategy = self.dateDecodingStrategy { - decoder.dateDecodingStrategy = strategy - } - do { - let storage = try decoder.decode(EntityContainer.self, from: data) - handler?(.success(storage)) - } catch let apiError as APIError { - CrashlyticsService.log(apiError) - handler?(.failure(apiError)) - return - } catch let decodingError as DecodingError { - CrashlyticsService.log(decodingError) - handler?(.failure(decodingError)) - return - } catch let error { - CrashlyticsService.log(error) - handler?(.failure(error)) - } - } - } - -} diff --git a/Campus-iOS/Base/Networking/APIResponse.swift b/Campus-iOS/Base/Networking/APIResponse.swift index 486b45be..f92933db 100644 --- a/Campus-iOS/Base/Networking/APIResponse.swift +++ b/Campus-iOS/Base/Networking/APIResponse.swift @@ -9,127 +9,45 @@ import Foundation import FirebaseCrashlytics -struct APIResponse: Decodable { - var response: ResponseType - - init(from decoder: Decoder) throws { - if let error = try? ErrorType(from: decoder) { - throw error - } else { - let response = try ResponseType(from: decoder) - self.response = response - } - } -} - -struct TUMOnlineAPIResponse: Decodable { - var rows: [T]? - - enum CodingKeys: String, CodingKey { - case rows = "row" - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.rows = try container.decode([Throwable].self, forKey: .rows).compactMap { - do { - return try $0.result.get() - } - catch { - CrashlyticsService.log(error) - return nil - } - } - } -} - -struct Throwable: Decodable { - let result: Result - - init(from decoder: Decoder) throws { - result = Result(catching: { try T(from: decoder) }) - } -} - -protocol APIError: Error, Decodable { } - -enum TUMOnlineAPIError: APIError, LocalizedError { - case noPermission - case tokenNotConfirmed - case invalidToken - case unkown(String) - - enum CodingKeys: String, CodingKey { - case message = "message" - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let error = try container.decode(String.self, forKey: .message) - - switch error { - case let str where str.contains("Keine Rechte für Funktion"): - self = .noPermission - case "Token ist nicht bestätigt!": - self = .tokenNotConfirmed - case "Token ist ungültig!": - self = .invalidToken - default: - self = .unkown(error) - } - } - - public var errorDescription: String? { - switch self { - case .noPermission: - return "No Permission".localized - case .tokenNotConfirmed: - return "Token not confirmed".localized - case .invalidToken: - return "Token invalid".localized - case let .unkown(message): - return "\("Unkonw error".localized): \(message)" - - } - } - - public var recoverySuggestion: String? { - switch self { - case .noPermission: - return "Make sure to enable the right permissions for your token.".localized - case .tokenNotConfirmed: - return "Go to TUMonline and confirm your token.".localized - case .invalidToken: - return "Try creating a new token.".localized - default: - return nil - } - } -} - - -enum TUMCabeAPIError: APIError, LocalizedError { - case unkown(String) - - enum CodingKeys: String, CodingKey { - case message - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let error = try container.decode(String.self, forKey: .message) - - switch error { - default: - self = .unkown(error) - } - } - - public var errorDescription: String? { - switch self { - case let .unkown(message): - return "\("Unkonw error".localized): \(message)" - } - } -} +//struct APIResponse: Decodable { +// var response: ResponseType +// +// init(from decoder: Decoder) throws { +// if let error = try? ErrorType(from: decoder) { +// throw error +// } else { +// let response = try ResponseType(from: decoder) +// self.response = response +// } +// } +//} +// +//struct TUMOnlineAPIResponse: Decodable { +// var rows: [T]? +// +// enum CodingKeys: String, CodingKey { +// case rows = "row" +// } +// +// init(from decoder: Decoder) throws { +// let container = try decoder.container(keyedBy: CodingKeys.self) +// self.rows = try container.decode([Throwable].self, forKey: .rows).compactMap { +// do { +// return try $0.result.get() +// } +// catch { +// CrashlyticsService.log(error) +// return nil +// } +// } +// } +//} +// +//struct Throwable: Decodable { +// let result: Result +// +// init(from decoder: Decoder) throws { +// result = Result(catching: { try T(from: decoder) }) +// } +//} diff --git a/Campus-iOS/CalendarComponent/Model/CalendarEvent+PreviewData.swift b/Campus-iOS/CalendarComponent/Model/CalendarEvent+PreviewData.swift new file mode 100644 index 00000000..4bb06ea3 --- /dev/null +++ b/Campus-iOS/CalendarComponent/Model/CalendarEvent+PreviewData.swift @@ -0,0 +1,66 @@ +// +// CalendarEvent+PreviewData.swift +// Campus-iOS +// +// Created by David Lin on 05.05.23. +// + +import Foundation + +extension CalendarEvent { + + static let mockEvent = CalendarEvent( + id: 1, + status: "FT", + url: URL(string: "https://campus.tum.de/tumonline/lv.detail?cLvNr=950369994"), + title: "Programmoptimierung (IN2053) VI", + descriptionText: "fix; Abhaltung; ", + startDate: Date(), + endDate: Date().addingTimeInterval(60 * 60), + location: "00.13.009A, Seminarraum (5613.EG.009A)" + ) + + static let previewData = [ + CalendarEvent( + id: 889413054, + status: "FT", + url: URL(string: "https://campus.tum.de/tumonline/lv.detail?cLvNr=950630619"), + title: "Grundlagen: Betriebssysteme und Systemsoftware (IN0009) VO", + descriptionText: "fix; Abhaltung; ", + startDate: DateFormatter.yyyyMMddhhmmss.date(from: "2022-12-21 13:00:00") ?? Date(), + endDate: DateFormatter.yyyyMMddhhmmss.date(from: "2022-12-21 14:00:00") ?? Date().addingTimeInterval(60 * 60), + location: "MW 0001, Gustav-Niemann-Hörsaal (5510.EG.001)" + ), + CalendarEvent( + id: 889461603, + status: "FT", + url: URL(string: "https://campus.tum.de/tumonline/lv.detail?cLvNr=950629892"), + title: "Analysis für Informatik [MA0902] VO", + descriptionText: "fix; Abhaltung; ", + startDate: DateFormatter.yyyyMMddhhmmss.date(from: "2022-12-22 08:30:00") ?? Date(), + endDate: DateFormatter.yyyyMMddhhmmss.date(from: "2022-12-22 10:00:00") ?? Date().addingTimeInterval(60 * 60), + location: "MW 0001, Gustav-Niemann-Hörsaal (5510.EG.001)" + ), + CalendarEvent( + id: 889413025, + status: "FT", + url: URL(string: "https://campus.tum.de/tumonline/lv.detail?cLvNr=950630619"), + title: "Grundlagen: Betriebssysteme und Systemsoftware (IN0009) VO", + descriptionText: "fix; Abhaltung; ", + startDate: DateFormatter.yyyyMMddhhmmss.date(from: "2023-01-09 13:45:00") ?? Date(), + endDate: DateFormatter.yyyyMMddhhmmss.date(from: "2023-01-09 15:15:00") ?? Date().addingTimeInterval(60 * 60), + location: "MW 2001 Rudolf-Diesel-Hörsaal (5510.02.001)" + ), + CalendarEvent( + id: 889461590, + status: "FT", + url: URL(string: "https://campus.tum.de/tumonline/lv.detail?cLvNr=950629892"), + title: "Analysis für Informatik [MA0902] VO", + descriptionText: "fix; Abhaltung; ", + startDate: DateFormatter.yyyyMMddhhmmss.date(from: "2023-01-10 08:30:00") ?? Date(), + endDate: DateFormatter.yyyyMMddhhmmss.date(from: "2023-01-10 10:00:00") ?? Date().addingTimeInterval(60 * 60), + location: "MW 0001, Gustav-Niemann-Hörsaal (5510.EG.001)" + ), + + ] +} diff --git a/Campus-iOS/CalendarComponent/Model/CalendarEvent.swift b/Campus-iOS/CalendarComponent/Model/CalendarEvent.swift index 2988f3af..c30db020 100644 --- a/Campus-iOS/CalendarComponent/Model/CalendarEvent.swift +++ b/Campus-iOS/CalendarComponent/Model/CalendarEvent.swift @@ -18,7 +18,9 @@ struct CalendarAPIResponse: Decodable { } } -struct CalendarEvent: Decodable, Identifiable, Equatable { + +struct CalendarEvent: Decodable, Identifiable, Equatable, Searchable { + var descriptionText: String? var endDate: Date? var id: Int64 @@ -28,6 +30,13 @@ struct CalendarEvent: Decodable, Identifiable, Equatable { var title: String? var url: URL? + var comparisonTokens: [ComparisonToken] { + return [ + ComparisonToken(value: title ?? ""), + ComparisonToken(value: location ?? ""), + ] + } + /* 886515989 @@ -166,16 +175,46 @@ class CustomEventDesign: EventViewGeneral { } } -extension CalendarEvent { - - static let mockEvent = CalendarEvent( - id: 1, - status: "FT", - url: URL(string: "https://campus.tum.de/tumonline/lv.detail?cLvNr=950369994"), - title: "Programmoptimierung (IN2053) VI", - descriptionText: "fix; Abhaltung; ", - startDate: Date(), - endDate: Date().addingTimeInterval(60 * 60), - location: "00.13.009A, Seminarraum (5613.EG.009A)" - ) -} + +/* + + 889413054 + FT + https://campus.tum.de/tumonline/lv.detail?cLvNr=950630619 + Grundlagen: Betriebssysteme und Systemsoftware (IN0009) VO + fix; Abhaltung; + 2022-12-21 13:00:00 + 2022-12-21 14:00:00 + MW 0001, Gustav-Niemann-Hörsaal (5510.EG.001) + + + 889461603 + FT + https://campus.tum.de/tumonline/lv.detail?cLvNr=950629892 + Analysis für Informatik [MA0902] VO + fix; Abhaltung; + 2022-12-22 08:30:00 + 2022-12-22 10:00:00 + MW 0001, Gustav-Niemann-Hörsaal (5510.EG.001) + + + 889413025 + FT + https://campus.tum.de/tumonline/lv.detail?cLvNr=950630619 + Grundlagen: Betriebssysteme und Systemsoftware (IN0009) VO + fix; Abhaltung; + 2023-01-09 13:45:00 + 2023-01-09 15:15:00 + MW 2001 Rudolf-Diesel-Hörsaal (5510.02.001) + + + 889461590 + FT + https://campus.tum.de/tumonline/lv.detail?cLvNr=950629892 + Analysis für Informatik [MA0902] VO + fix; Abhaltung; + 2023-01-10 08:30:00 + 2023-01-10 10:00:00 + MW 0001, Gustav-Niemann-Hörsaal (5510.EG.001) + + */ diff --git a/Campus-iOS/CalendarComponent/Screen/CalendarScreen.swift b/Campus-iOS/CalendarComponent/Screen/CalendarScreen.swift index 7fafb2ed..eec08d5f 100644 --- a/Campus-iOS/CalendarComponent/Screen/CalendarScreen.swift +++ b/Campus-iOS/CalendarComponent/Screen/CalendarScreen.swift @@ -59,7 +59,7 @@ struct CalendarScreen: View { } } .alert( - "Error while fetching Grades", + "Error while fetching Calendar", isPresented: $vm.hasError, presenting: vm.state) { detail in Button("Retry") { diff --git a/Campus-iOS/CalendarComponent/Service/CalendarService.swift b/Campus-iOS/CalendarComponent/Service/CalendarService.swift index 94fb8889..7f791bca 100644 --- a/Campus-iOS/CalendarComponent/Service/CalendarService.swift +++ b/Campus-iOS/CalendarComponent/Service/CalendarService.swift @@ -7,7 +7,12 @@ import Foundation -struct CalendarService: ServiceTokenProtocol { +protocol CalendarServiceProtocol { + func fetch(token: String, forcedRefresh: Bool) async throws -> [CalendarEvent] +} + +struct CalendarService: ServiceTokenProtocol, CalendarServiceProtocol { + func fetch(token: String, forcedRefresh: Bool = false) async throws -> [CalendarEvent] { let response: TUMOnlineAPI.CalendarResponse = try await MainAPI.makeRequest(endpoint: TUMOnlineAPI.calendar, token: token, forcedRefresh: forcedRefresh) diff --git a/Campus-iOS/DataModels/DataTypeClassifierV4English.mlmodel b/Campus-iOS/DataModels/DataTypeClassifierV4English.mlmodel new file mode 100644 index 00000000..cab2e5ef Binary files /dev/null and b/Campus-iOS/DataModels/DataTypeClassifierV4English.mlmodel differ diff --git a/Campus-iOS/DataModels/DataTypeClassifierV4German.mlmodel b/Campus-iOS/DataModels/DataTypeClassifierV4German.mlmodel new file mode 100644 index 00000000..7fbb3bfb Binary files /dev/null and b/Campus-iOS/DataModels/DataTypeClassifierV4German.mlmodel differ diff --git a/Campus-iOS/DataModels/Old models/DataTypeClassifierV1.mlmodel b/Campus-iOS/DataModels/Old models/DataTypeClassifierV1.mlmodel new file mode 100644 index 00000000..2fb59437 Binary files /dev/null and b/Campus-iOS/DataModels/Old models/DataTypeClassifierV1.mlmodel differ diff --git a/Campus-iOS/DataModels/Old models/DataTypeClassifierV2.mlmodel b/Campus-iOS/DataModels/Old models/DataTypeClassifierV2.mlmodel new file mode 100644 index 00000000..c2ed15dc Binary files /dev/null and b/Campus-iOS/DataModels/Old models/DataTypeClassifierV2.mlmodel differ diff --git a/Campus-iOS/DataModels/Old models/DataTypeClassifierV3.mlmodel b/Campus-iOS/DataModels/Old models/DataTypeClassifierV3.mlmodel new file mode 100644 index 00000000..eb567c15 Binary files /dev/null and b/Campus-iOS/DataModels/Old models/DataTypeClassifierV3.mlmodel differ diff --git a/Campus-iOS/GradesComponent/Model/Grade+PreviewData.swift b/Campus-iOS/GradesComponent/Model/Grade+PreviewData.swift new file mode 100644 index 00000000..17b2fb76 --- /dev/null +++ b/Campus-iOS/GradesComponent/Model/Grade+PreviewData.swift @@ -0,0 +1,34 @@ +// +// Grade+PreviewData.swift +// Campus-iOS +// +// Created by David Lin on 05.05.23. +// + +import Foundation + +extension Grade { + static let dummyData21W: [Grade] = [ + Grade(date: .now, lvNumber: "IN0008", semester: "21W", title: "Grundlagen: Datenbanken", examiner: "Kemper", grade: "1,3", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170) + ] + + static let dummyData21S: [Grade] = [ + Grade(date: .now, lvNumber: "IN0010E", semester: "21S", title: "Grundlagen: Rechnernetze und verteilte Systeme", examiner: "Carle", grade: "2,7", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170), + Grade(date: .now, lvNumber: "IN0006", semester: "21S", title: "Einführung in die Softwaretechnik", examiner: "Brügge", grade: "2,7", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170) + ] + + static let dummyData20W: [Grade] = [ + Grade(date: .now, lvNumber: "IN0001E", semester: "20W", title: "Einführung in die Informatik 1", examiner: "Seidl", grade: "2,3", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170), + Grade(date: .now, lvNumber: "IN0002", semester: "20W", title: "Praktikum Grundlagen der Programmierung", examiner: "Seidl", grade: "1,7", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170) + ] + + static var previewData: [Grade] = dummyData21W + dummyData21S + dummyData20W + + static let dummyData: [Grade] = [ + Grade(date: .now, lvNumber: "IN4741", semester: "17W", title: "Seminar Teaching iOS", examiner: "Brügge", grade: "1,0", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170), + Grade(date: .now, lvNumber: "IN4741", semester: "17W", title: "Seminar Teaching iOS", examiner: "Brügge", grade: "1,0", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170), + Grade(date: .now, lvNumber: "IN4741", semester: "17W", title: "Seminar Teaching iOS", examiner: "Brügge", grade: "1,0", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170) + ] + + static var dummyDataAll: [Grade] = dummyData21W + dummyData21S + dummyData20W +} diff --git a/Campus-iOS/GradesComponent/Model/Grade.swift b/Campus-iOS/GradesComponent/Model/Grade.swift index 9dc852ba..9d3d55a1 100644 --- a/Campus-iOS/GradesComponent/Model/Grade.swift +++ b/Campus-iOS/GradesComponent/Model/Grade.swift @@ -7,11 +7,12 @@ import Foundation -struct Grade: Decodable, Identifiable { +struct Grade: Decodable, Identifiable, Searchable { // Create own identifier as there isn't one public var id: String { date.formatted() + "-" + lvNumber } + public var date: Date public var lvNumber: String public var semester: String @@ -24,6 +25,20 @@ struct Grade: Decodable, Identifiable { public var studyDesignation: String public var studyNumber: UInt64 + var comparisonTokens: [ComparisonToken] { + get { + return [ + ComparisonToken(value: title), + ComparisonToken(value: examiner), + ComparisonToken(value: grade, type: .raw), + ComparisonToken(value: lvNumber), + ComparisonToken(value: modus), + ComparisonToken(value: semester), + ComparisonToken(value: studyDesignation) + ] + } + } + var modusShort: String { switch self.modus { case "Schriftlich": return "Written".localized @@ -85,27 +100,3 @@ struct Grade: Decodable, Identifiable { } } -extension Grade { - static let dummyData21W: [Grade] = [ - Grade(date: .now, lvNumber: "IN0008", semester: "21W", title: "Grundlagen: Datenbanken", examiner: "Kemper", grade: "1,3", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170) - ] - - static let dummyData21S: [Grade] = [ - Grade(date: .now, lvNumber: "IN0010E", semester: "21S", title: "Grundlagen: Rechnernetze und verteilte Systeme", examiner: "Carle", grade: "2,7", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170), - Grade(date: .now, lvNumber: "IN0006", semester: "21S", title: "Einführung in die Softwaretechnik", examiner: "Brügge", grade: "2,7", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170) - ] - - static let dummyData20W: [Grade] = [ - Grade(date: .now, lvNumber: "IN0001E", semester: "20W", title: "Einführung in die Informatik 1", examiner: "Seidl", grade: "2,3", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170), - Grade(date: .now, lvNumber: "IN0002", semester: "20W", title: "Praktikum Grundlagen der Programmierung", examiner: "Seidl", grade: "1,7", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170) - ] - - static var dummyDataAll: [Grade] = dummyData21W + dummyData21S + dummyData20W - - static let dummyData: [Grade] = [ - Grade(date: .now, lvNumber: "IN4741", semester: "17W", title: "Seminar Teaching iOS", examiner: "Brügge", grade: "1,0", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170), - Grade(date: .now, lvNumber: "IN4741", semester: "17W", title: "Seminar Teaching iOS", examiner: "Brügge", grade: "1,0", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170), - Grade(date: .now, lvNumber: "IN4741", semester: "17W", title: "Seminar Teaching iOS", examiner: "Brügge", grade: "1,0", examType: "FA", modus: "Schriftlich", studyID: "1630 17 030", studyDesignation: "Informatik", studyNumber: 947170) - ] -} - diff --git a/Campus-iOS/GradesComponent/Service/GradesService.swift b/Campus-iOS/GradesComponent/Service/GradesService.swift index 683af8be..db52ae26 100644 --- a/Campus-iOS/GradesComponent/Service/GradesService.swift +++ b/Campus-iOS/GradesComponent/Service/GradesService.swift @@ -8,10 +8,20 @@ import Foundation import Alamofire -struct GradesService: ServiceTokenProtocol { +protocol GradesServiceProtocol { + func fetch(token: String, forcedRefresh: Bool) async throws -> [Grade] +} + +typealias GradesSemesterDegrees = [(String, [(String, [Grade])])] + +struct GradesService: ServiceTokenProtocol, GradesServiceProtocol { + func fetch(token: String, forcedRefresh: Bool = false) async throws -> [Grade] { let response: TUMOnlineAPI.Response = try await MainAPI.makeRequest(endpoint: TUMOnlineAPI.personalGrades, token: token, forcedRefresh: forcedRefresh) return response.row + .sorted { gradeA, gradeB in + return gradeA.date > gradeB.date + } } } diff --git a/Campus-iOS/LectureComponent/Model/Lecture.swift b/Campus-iOS/LectureComponent/Model/Lecture.swift index 344a925d..b03dc9a7 100644 --- a/Campus-iOS/LectureComponent/Model/Lecture.swift +++ b/Campus-iOS/LectureComponent/Model/Lecture.swift @@ -7,7 +7,7 @@ import Foundation -struct Lecture: Decodable, Identifiable, Equatable { +struct Lecture: Decodable, Identifiable, Equatable, Searchable { public var id: UInt64 public var lvNumber: UInt64 public var title: String @@ -41,6 +41,16 @@ struct Lecture: Decodable, Identifiable, Equatable { } } + var comparisonTokens: [ComparisonToken] { + return [ + ComparisonToken(value: title), + ComparisonToken(value: semesterID, type: .raw), + ComparisonToken(value: organisation), + ComparisonToken(value: speaker), + ComparisonToken(value: semester) + ] + } + enum CodingKeys: String, CodingKey { case id = "stp_sp_nr" case lvNumber = "stp_lv_nr" @@ -64,7 +74,44 @@ struct Lecture: Decodable, Identifiable, Equatable { extension Lecture { static let dummyData: [Lecture] = [ Lecture(id: 950396293, lvNumber: 90049615, title: "Practical course - Program optimization with LLVM (IN0012, IN2106, IN4236)", duration: "6", stp_sp_sst: "6", eventTypeDefault: "Praktikum", eventTypeTag: "PR", semesterYear: "2018/19", semesterType: "W", semester: "Wintersemester 2018/19", semesterID: "18W", organisationNumber: 15427, organisation: "Informatik 2 - Lehrstuhl für Sprachen und Beschreibungsstrukturen in der Informatik (Prof. Seidl)", organisationTag: "TUINI02", speaker: "Seidl H [L], Petter M"), - Lecture(id: 950396293, lvNumber: 90049615, title: "Practical course - Program optimization with LLVM (IN0012, IN2106, IN4236)", duration: "6", stp_sp_sst: "6", eventTypeDefault: "Praktikum", eventTypeTag: "PR", semesterYear: "2018/19", semesterType: "W", semester: "Wintersemester 2018/19", semesterID: "18W", organisationNumber: 15427, organisation: "Informatik 2 - Lehrstuhl für Sprachen und Beschreibungsstrukturen in der Informatik (Prof. Seidl)", organisationTag: "TUINI02", speaker: "Seidl H [L], Petter M"), - Lecture(id: 950396293, lvNumber: 90049615, title: "Practical course - Program optimization with LLVM (IN0012, IN2106, IN4236)", duration: "6", stp_sp_sst: "6", eventTypeDefault: "Praktikum", eventTypeTag: "PR", semesterYear: "2018/19", semesterType: "W", semester: "Wintersemester 2018/19", semesterID: "18W", organisationNumber: 15427, organisation: "Informatik 2 - Lehrstuhl für Sprachen und Beschreibungsstrukturen in der Informatik (Prof. Seidl)", organisationTag: "TUINI02", speaker: "Seidl H [L], Petter M") + Lecture(id: 950630619, lvNumber: 40984991, title: "Grundlagen: Betriebssysteme und Systemsoftware (IN0009)", duration: "3", stp_sp_sst: "3", eventTypeDefault: "Vorlesung", eventTypeTag: "VO", semesterYear: "2022/23", semesterType: "W", semester: "Wintersemester 2022/23", semesterID: "22W", organisationNumber: 47337, organisation: "Informatik 11 - Lehrstuhl für Connected Mobility (Prof. Ott)", organisationTag: "TUINI24", speaker: "Ott J [L], Ott J, Uhl M"), + Lecture(id: 950629892, lvNumber: 20007563, title: "Analysis für Informatik [MA0902]", duration: "4", stp_sp_sst: "4", eventTypeDefault: "Vorlesung", eventTypeTag: "VO", semesterYear: "2022/23", semesterType: "W", semester: "Wintersemester 2022/23", semesterID: "22W", organisationNumber: 53597, organisation: "Department of Mathematics", organisationTag: "TUS1DP1", speaker: "Rolles S") ] } + +/* + +950630619 +40984991 +Grundlagen: Betriebssysteme und Systemsoftware (IN0009) +3 +3 +Vorlesung +VO +2022/23 +W +Wintersemester 2022/23 +22W +47337 +Informatik 11 - Lehrstuhl für Connected Mobility (Prof. Ott) +TUINI24 +Ott J [L], Ott J, Uhl M + + +950629892 +20007563 +Analysis für Informatik [MA0902] +4 +4 +Vorlesung +VO +2022/23 +W +Wintersemester 2022/23 +22W +53597 +Department of Mathematics +TUS1DP1 +Rolles S + +*/ diff --git a/Campus-iOS/LectureComponent/Views/LectureDetailsViews/LectureDetailsEventInfoView.swift b/Campus-iOS/LectureComponent/Views/LectureDetailsViews/LectureDetailsEventInfoView.swift index 90bb5443..41db84c2 100644 --- a/Campus-iOS/LectureComponent/Views/LectureDetailsViews/LectureDetailsEventInfoView.swift +++ b/Campus-iOS/LectureComponent/Views/LectureDetailsViews/LectureDetailsEventInfoView.swift @@ -68,14 +68,14 @@ struct LectureDetailsEventInfoView: View { ) } - private static let startDateFormatter: DateFormatter = { + static let startDateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.locale = Locale.current formatter.dateFormat = "EE, dd.MM.yyyy, HH:mm" return formatter }() - private static let endDateFormatter: DateFormatter = { + static let endDateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.locale = Locale.current formatter.dateFormat = "HH:mm" diff --git a/Campus-iOS/LoginComponent/Service/AuthenticationHandler.swift b/Campus-iOS/LoginComponent/Service/AuthenticationHandler.swift index f1b22657..67dfd054 100644 --- a/Campus-iOS/LoginComponent/Service/AuthenticationHandler.swift +++ b/Campus-iOS/LoginComponent/Service/AuthenticationHandler.swift @@ -33,6 +33,7 @@ enum LoginError: LocalizedError { } } + class AuthenticationHandler { private static let keychain = Keychain(service: "de.tum.campusapp") .synchronizable(true) @@ -85,7 +86,7 @@ class AuthenticationHandler { case .tumID(tumID: _, token: let token), .tumIDAndKey(tumID: _, token: let token, key: _): do { let confirmation: Confirmation = try await MainAPI.makeRequest(endpoint: TUMOnlineAPI.tokenConfirmation, token: token, forcedRefresh: true) - + if confirmation.value { return .success(true) } else { @@ -113,3 +114,9 @@ class AuthenticationHandler { credentials = .noTumID } } + +class AuthenticationHandler_Preview: AuthenticationHandler { + override var credentials: Credentials? { + return .tumID(tumID: "Preview_TUMID", token: "Preview_Token") + } +} diff --git a/Campus-iOS/MapComponent/Service/CafeteriasService.swift b/Campus-iOS/MapComponent/Service/CafeteriasService.swift index fcb35451..6f657ecd 100644 --- a/Campus-iOS/MapComponent/Service/CafeteriasService.swift +++ b/Campus-iOS/MapComponent/Service/CafeteriasService.swift @@ -8,7 +8,11 @@ import Foundation import Alamofire -struct CafeteriasService: ServiceProtocol { +protocol CafeteriasServiceProtocol { + func fetch(forcedRefresh: Bool) async throws -> [Cafeteria] +} + +struct CafeteriasService: ServiceProtocol, CafeteriasServiceProtocol { typealias T = Cafeteria func fetch(forcedRefresh: Bool) async throws -> [Cafeteria] { diff --git a/Campus-iOS/MapComponent/Service/StudyRoomsService.swift b/Campus-iOS/MapComponent/Service/StudyRoomsService.swift index 48115893..af666376 100644 --- a/Campus-iOS/MapComponent/Service/StudyRoomsService.swift +++ b/Campus-iOS/MapComponent/Service/StudyRoomsService.swift @@ -7,7 +7,11 @@ import Foundation -struct StudyRoomsService { +protocol StudyRoomsServiceProtocol { + func fetch(forcedRefresh: Bool) async throws -> StudyRoomApiRespose +} + +struct StudyRoomsService: StudyRoomsServiceProtocol { func fetch(forcedRefresh: Bool) async throws -> StudyRoomApiRespose { let response: StudyRoomApiRespose = try await MainAPI.makeRequest(endpoint: TUMDevAppAPI.rooms, forcedRefresh: forcedRefresh) diff --git a/Campus-iOS/MapComponent/Types/Cafeterias/Cafeteria+PreviewData.swift b/Campus-iOS/MapComponent/Types/Cafeterias/Cafeteria+PreviewData.swift new file mode 100644 index 00000000..74de69af --- /dev/null +++ b/Campus-iOS/MapComponent/Types/Cafeterias/Cafeteria+PreviewData.swift @@ -0,0 +1,17 @@ +// +// Cafeteria+PreviewData.swift +// Campus-iOS +// +// Created by David Lin on 05.05.23. +// + +import Foundation + +extension Cafeteria { + static let previewData = [ + Cafeteria(location: Location(latitude: 48.147420, longitude: 11.567220, address: "Arcisstraße 17, München"), name: "Mensa Arcisstraße", id: "mensa-arcisstr", queueStatusApi: nil, queue: nil), + Cafeteria(location: Location(latitude: 0.0, longitude: 0.0, address: "xystraße, München"), name: "Mensa XY", id: "mensa-xy", queueStatusApi: nil, queue: nil), + // Mensa Garching has an maximum capacity of 1500 people (calculated reversed with the current and percentage of a day, e.g. current = 2 and percent: 0.13333334 then 0.13333334/100 * x = 2 => 2/(13333334/100) = 1500) + Cafeteria(location: Location(latitude: 48.268132, longitude: 11.672263, address: "Boltzmannstraße 19, Garching"), name: "Mensa Garching", id: "mensa-garching", queueStatusApi: Optional("https://mensa.liste.party/api/"), queue: Queue(current: 150, percent: 0.1)) + ] +} diff --git a/Campus-iOS/MapComponent/Types/Cafeterias/Cafeteria.swift b/Campus-iOS/MapComponent/Types/Cafeterias/Cafeteria.swift index 3065e4f7..106674d3 100644 --- a/Campus-iOS/MapComponent/Types/Cafeterias/Cafeteria.swift +++ b/Campus-iOS/MapComponent/Types/Cafeterias/Cafeteria.swift @@ -21,7 +21,7 @@ struct Queue: Decodable, Hashable { let percent: Float } -struct Cafeteria: Decodable, Hashable { +struct Cafeteria: Decodable, Searchable, Identifiable { /* "location": { "address": "Arcisstraße 17, München", @@ -50,6 +50,14 @@ struct Cafeteria: Decodable, Hashable { case queue } + var comparisonTokens: [ComparisonToken] { + return [ + ComparisonToken(value: name), + ComparisonToken(value: location.address), + ComparisonToken(value: String(location.latitude), type: .raw), + ComparisonToken(value: String(location.longitude), type: .raw) + ] + } } extension Array where Element == Cafeteria { @@ -61,9 +69,3 @@ extension Array where Element == Cafeteria { } } } - -let mockCafeterias = [ - Cafeteria(location: Location(latitude: 48.147420, longitude: 11.567220, address: "Arcisstraße 17, München"), name: "Mensa Arcisstraße", id: "mensa-arcisstr", queueStatusApi: nil, queue: nil), - // Mensa Garching has an maximum capacity of 1500 people (reversed calculated with the current and percentage of a day, e.g. current = 2 and percent: 0.13333334 then 0.13333334/100 * x = 2 => 2/(13333334/100) = 1500) - Cafeteria(location: Location(latitude: 48.268132, longitude: 11.672263, address: "Boltzmannstraße 19, Garching"), name: "Mensa Garching", id: "mensa-garching", queueStatusApi: Optional("https://mensa.liste.party/api/"), queue: Queue(current: 150, percent: 0.1)) -] diff --git a/Campus-iOS/MapComponent/Types/Cafeterias/MensaCategory.swift b/Campus-iOS/MapComponent/Types/Cafeterias/MensaCategory.swift new file mode 100644 index 00000000..478432db --- /dev/null +++ b/Campus-iOS/MapComponent/Types/Cafeterias/MensaCategory.swift @@ -0,0 +1,19 @@ +// +// MensaCategory.swift +// Campus-iOS +// +// Created by David Lin on 02.04.23. +// + +import Foundation + +struct MenuCategory: Identifiable, Decodable { + var id = UUID() + let name: String + let dishes: [Dish] + + init(name: String, dishes: [Dish]) { + self.name = name + self.dishes = dishes + } +} diff --git a/Campus-iOS/MapComponent/ViewModel/MenuViewModel.swift b/Campus-iOS/MapComponent/Types/Cafeterias/Menu.swift similarity index 74% rename from Campus-iOS/MapComponent/ViewModel/MenuViewModel.swift rename to Campus-iOS/MapComponent/Types/Cafeterias/Menu.swift index b6b6b4fa..0b2b789d 100644 --- a/Campus-iOS/MapComponent/ViewModel/MenuViewModel.swift +++ b/Campus-iOS/MapComponent/Types/Cafeterias/Menu.swift @@ -29,14 +29,3 @@ final class Menu: Identifiable, Decodable { return dishes.sorted(by: { $0.name < $1.name }) } } - -struct MenuCategory: Identifiable, Decodable { - var id = UUID() - let name: String - let dishes: [Dish] - - init(name: String, dishes: [Dish]) { - self.name = name - self.dishes = dishes - } -} diff --git a/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoom.swift b/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoom.swift index f4cf5533..45b90682 100644 --- a/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoom.swift +++ b/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoom.swift @@ -8,7 +8,25 @@ import Foundation import SwiftUI -struct StudyRoom: Decodable { +struct StudyRoom: Decodable, Identifiable, Searchable { + + static func == (lhs: StudyRoom, rhs: StudyRoom) -> Bool { + lhs.id == rhs.id + } + + var comparisonTokens: [ComparisonToken] { + get { + return [ + ComparisonToken(value: name ?? ""), + ComparisonToken(value: buildingCode ?? "", type: .raw), + ComparisonToken(value: buildingName ?? ""), + ComparisonToken(value: String(buildingNumber), type: .raw), + ComparisonToken(value: status ?? ""), + ComparisonToken(value: occupiedBy ?? "") + ] + (attributes?.flatMap { $0.comparisonTokens } ?? []) + } + } + var buildingCode: String? var buildingName: String? var buildingNumber: Int64 @@ -148,4 +166,23 @@ struct StudyRoom: Decodable { func isAvailable() -> Bool { return status == "frei" } + + init(buildingCode: String? = nil, buildingName: String? = nil, buildingNumber: Int64, code: String? = nil, id: Int64, name: String? = nil, number: String? = nil, occupiedBy: String? = nil, occupiedFor: Int64, occupiedFrom: String? = nil, occupiedIn: Int64, occupiedUntil: String? = nil, raum_nr_architekt: String? = nil, res_nr: Int64, status: String? = nil, attributes: [StudyRoomAttribute]? = nil) { + self.buildingCode = buildingCode + self.buildingName = buildingName + self.buildingNumber = buildingNumber + self.code = code + self.id = id + self.name = name + self.number = number + self.occupiedBy = occupiedBy + self.occupiedFor = occupiedFor + self.occupiedFrom = occupiedFrom + self.occupiedIn = occupiedIn + self.occupiedUntil = occupiedUntil + self.raum_nr_architekt = raum_nr_architekt + self.res_nr = res_nr + self.status = status + self.attributes = attributes + } } diff --git a/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomApiResponse+PreviewData.swift b/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomApiResponse+PreviewData.swift new file mode 100644 index 00000000..69b13359 --- /dev/null +++ b/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomApiResponse+PreviewData.swift @@ -0,0 +1,18 @@ +// +// StudyRoomApiResponse+PreviewData.swift +// Campus-iOS +// +// Created by David Lin on 05.05.23. +// + +import Foundation + +extension StudyRoomApiRespose { + static let previewData: StudyRoomApiRespose = StudyRoomApiRespose( + rooms: [ +StudyRoom(buildingCode: "5603", buildingName: "FMI/ Bibliothek", buildingNumber: 832, code: "01.03.010", id: 49770, name: "Windfang", number: "010", occupiedBy: "", occupiedFor: 0, occupiedFrom: "", occupiedIn: 0, occupiedUntil: "", raum_nr_architekt: "01.03.010@5603", res_nr: 27052, status: "frei", attributes: []), + StudyRoom(buildingCode: "5603", buildingName: "FMI/ Bibliothek", buildingNumber: 832, code: "01.03.010", id: 58380, name: "Einzelarbeitsraum", number: "043B", occupiedBy: "", occupiedFor: 0, occupiedFrom: "", occupiedIn: 0, occupiedUntil: "", raum_nr_architekt: "01.03.043B@5603", res_nr: 26714, status: "frei", attributes: [])], + groups: [ + StudyRoomGroup(detail: "", id: 46, name: "Fachschaftsräume Mathe/Informatik", sorting: 1, rooms: [49770,58380,58372,58363,58354,58346,58227,58217,58211,58203,58195,58187,58068,58059,58050,58041])]) + +} diff --git a/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomApiResponse.swift b/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomApiResponse.swift index 2377c0e0..aa5b4a24 100644 --- a/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomApiResponse.swift +++ b/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomApiResponse.swift @@ -35,4 +35,18 @@ struct StudyRoomApiRespose: Decodable, Equatable { self.rooms = rooms self.groups = groups } + + init(rooms: [StudyRoom]?, groups: [StudyRoomGroup]?) { + self.rooms = rooms + self.groups = groups + } } + +/* + {"nr":46,"sortierung":1,"name":"Fachschaftsr\u00e4ume Mathe\/Informatik","detail":"","raeume":[49770,58380,58372,58363,58354,58346,58227,58217,58211,58203,58195,58187,58068,58059,58050,58041],"regionen":["gar_f"]} + */ + +/* + {"raum_nr":49770,"raum_code":"01.03.010","raum_nummer":"010","raum_nr_architekt":"01.03.010@5603","belegung_bis":"","belegung_fuer":0,"belegung_ab":"","belegung_in":0,"belegung_durch":"","raum_name":"Windfang","gebaeude_nr":832,"gebaeude_code":"5603","gebaeude_name":"FMI\/ Bibliothek","status":"frei","res_nr":27052,"attribute":[]}, + "raum_nr":58380,"raum_code":"01.03.043B","raum_nummer":"043B","raum_nr_architekt":"01.03.043B@5603","belegung_bis":"","belegung_fuer":0,"belegung_ab":"","belegung_in":0,"belegung_durch":"","raum_name":"Einzelarbeitsraum","gebaeude_nr":832,"gebaeude_code":"5603","gebaeude_name":"FMI\/ Bibliothek","status":"frei","res_nr":26714,"attribute":[]} + */ diff --git a/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomAttribute.swift b/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomAttribute.swift index 24f8d1e7..276a8a97 100644 --- a/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomAttribute.swift +++ b/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomAttribute.swift @@ -7,7 +7,8 @@ import Foundation -struct StudyRoomAttribute: Decodable { +struct StudyRoomAttribute: Decodable, Searchable { + var detail: String? var name: String? @@ -25,4 +26,11 @@ struct StudyRoomAttribute: Decodable { self.detail = detail self.name = name } + + var comparisonTokens: [ComparisonToken] { + return [ + ComparisonToken(value: detail ?? ""), + ComparisonToken(value: name ?? "") + ] + } } diff --git a/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomGroup.swift b/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomGroup.swift index 2f58ba3e..33a3b126 100644 --- a/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomGroup.swift +++ b/Campus-iOS/MapComponent/Types/StudyRooms/StudyRoomGroup.swift @@ -8,7 +8,14 @@ import Foundation import MapKit -struct StudyRoomGroup: Decodable, Equatable { +struct StudyRoomGroup: Decodable, Equatable, Searchable { + var comparisonTokens: [ComparisonToken] { + return [ + ComparisonToken(value: name ?? ""), + ComparisonToken(value: detail ?? "") + ] + } + var detail: String? var id: Int64 var name: String? @@ -63,6 +70,14 @@ struct StudyRoomGroup: Decodable, Equatable { self.rooms = room_nrs } + init(detail: String? = nil, id: Int64, name: String? = nil, sorting: Int64, rooms: [Int64]? = nil) { + self.detail = detail + self.id = id + self.name = name + self.sorting = sorting + self.rooms = rooms + } + func getRooms(allRooms rooms: [StudyRoom]) -> [StudyRoom]? { rooms.filter({ self.rooms?.contains($0.id) ?? false }) } diff --git a/Campus-iOS/MapComponent/View/Cafeterias/CafeteriaRowView.swift b/Campus-iOS/MapComponent/View/Cafeterias/CafeteriaRowView.swift index 2a1e6ec4..8933c21d 100644 --- a/Campus-iOS/MapComponent/View/Cafeterias/CafeteriaRowView.swift +++ b/Campus-iOS/MapComponent/View/Cafeterias/CafeteriaRowView.swift @@ -66,7 +66,7 @@ struct CafeteriaRowView: View { struct CafeteriaRowView_Previews: PreviewProvider { static var previews: some View { - CafeteriaRowView(cafeteria: .constant(mockCafeterias[0])) + CafeteriaRowView(cafeteria: .constant(Cafeteria.previewData[0])) .previewLayout(PreviewLayout.sizeThatFits) } } diff --git a/Campus-iOS/MapComponent/View/Cafeterias/CafeteriaView.swift b/Campus-iOS/MapComponent/View/Cafeterias/CafeteriaView.swift index e1efd835..536ce21b 100644 --- a/Campus-iOS/MapComponent/View/Cafeterias/CafeteriaView.swift +++ b/Campus-iOS/MapComponent/View/Cafeterias/CafeteriaView.swift @@ -27,7 +27,7 @@ struct CafeteriaView: View { if let canteen = self.selectedCanteen { VStack { HStack{ - VStack(alignment: .leading){ + VStack(alignment: .leading) { Text(canteen.name) .bold() .font(.title3) diff --git a/Campus-iOS/MapComponent/View/Cafeterias/CafeteriaWidgetView.swift b/Campus-iOS/MapComponent/View/Cafeterias/CafeteriaWidgetView.swift index eed863f5..81664870 100644 --- a/Campus-iOS/MapComponent/View/Cafeterias/CafeteriaWidgetView.swift +++ b/Campus-iOS/MapComponent/View/Cafeterias/CafeteriaWidgetView.swift @@ -34,6 +34,7 @@ struct CafeteriaWidgetView: View { default: if let cafeteria = viewModel.cafeteria, let title = cafeteria.title { + CafeteriaWidgetContent( size: size, cafeteria: title, @@ -163,16 +164,18 @@ struct CompactMenuView: View { struct CompactDishView: View { - @StateObject var vm: DishViewModel + @StateObject var vm: DishViewModel = DishViewModel() + let dish: Dish + init(dish: Dish) { - self._vm = StateObject(wrappedValue: DishViewModel(dish: dish)) + self.dish = dish } var body: some View { VStack(alignment: .leading) { - Text(vm.dish.name) + Text(dish.name) .lineLimit(1) - Text(vm.formatPrice(dish: vm.dish, pricingGroup: "students")) + Text(vm.formatPrice(dish: dish, pricingGroup: "students")) .font(.caption) .bold() } diff --git a/Campus-iOS/MapComponent/View/Cafeterias/DishView.swift b/Campus-iOS/MapComponent/View/Cafeterias/DishView.swift index 5196cb75..caa48cad 100644 --- a/Campus-iOS/MapComponent/View/Cafeterias/DishView.swift +++ b/Campus-iOS/MapComponent/View/Cafeterias/DishView.swift @@ -7,54 +7,47 @@ import SwiftUI +@MainActor struct DishView: View { - @StateObject var vm: DishViewModel + @StateObject var vm: DishViewModel = DishViewModel() @State private var isExpanded = false - init(dish: Dish) { - self._vm = StateObject(wrappedValue: DishViewModel(dish: dish)) + let dish: Dish + let dishLabels: [String : DishLabel] + + init(dish: Dish, dishLabels: [String : DishLabel]) { + self.dish = dish + self.dishLabels = dishLabels } var body: some View { - Group { - switch vm.state { - case .success(data: let generalLabels): - DisclosureGroup(isExpanded: $isExpanded) { - HStack{ - VStack{ - ForEach(vm.dish.labels, id: \.self) { label in - Text(vm.labelToAbbreviation(generalLabel: generalLabels, label: label)) - } - } - .padding(.trailing, 10.0) - VStack(alignment: .leading){ - ForEach(vm.dish.labels, id: \.self) { label in - Text(vm.labelToDescription(generalLabel: generalLabels, label: label)) - } - } + DisclosureGroup(isExpanded: $isExpanded) { + HStack{ + VStack{ + ForEach(dish.labels, id: \.self) { label in + Text(vm.labelToAbbreviation(generalLabel: dishLabels, label: label)) } - } label: { - VStack(alignment: .leading, spacing: 10){ - Spacer().frame(height: 0) - Text(vm.dish.name).bold() - HStack{ - Spacer() - Text(vm.formatPrice(dish: vm.dish, pricingGroup: "students")) - .lineLimit(1) - .font(.system(size: 15)) - } - Spacer().frame(height: 0) + } + .padding(.trailing, 10.0) + VStack(alignment: .leading){ + ForEach(dish.labels, id: \.self) { label in + Text(vm.labelToDescription(generalLabel: dishLabels, label: label)) } } - .buttonStyle(PlainButtonStyle()).disabled(true) - case .loading, .na: - LoadingView(text: "Fetching Dish Labels") - case .failed(error: let error): - FailedView(errorDescription: error.localizedDescription, retryClosure: vm.getDishLabels - ) } - }.task { - await vm.getDishLabels() + } label: { + VStack(alignment: .leading, spacing: 10){ + Spacer().frame(height: 0) + Text(dish.name).bold() + HStack{ + Spacer() + Text(vm.formatPrice(dish: dish, pricingGroup: "students")) + .lineLimit(1) + .font(.system(size: 15)) + } + Spacer().frame(height: 0) + } } + .buttonStyle(PlainButtonStyle()).disabled(true) } } diff --git a/Campus-iOS/MapComponent/View/Cafeterias/MealPlanScreen.swift b/Campus-iOS/MapComponent/View/Cafeterias/MealPlanScreen.swift index 028333cd..a228a5de 100644 --- a/Campus-iOS/MapComponent/View/Cafeterias/MealPlanScreen.swift +++ b/Campus-iOS/MapComponent/View/Cafeterias/MealPlanScreen.swift @@ -8,6 +8,7 @@ import SwiftUI struct MealPlanScreen: View { + @Environment(\.colorScheme) var colorScheme @StateObject var vm: MealPlanViewModel init(cafeteria: Cafeteria) { @@ -20,7 +21,7 @@ struct MealPlanScreen: View { case .success(let menus): if let firstMenu = menus.first { VStack { - MealPlanView(menus: menus, cafeteria: vm.cafeteria, selectedMenu: firstMenu) .refreshable { + MealPlanView(menus: menus, cafeteria: vm.cafeteria, selectedMenu: firstMenu).refreshable { await vm.getMenus() } } @@ -31,7 +32,7 @@ struct MealPlanScreen: View { VStack { Spacer() // Since some cafeterias do not update their menus this is how we handle error here. There could be a better differentiation. - Text("No Menu available") + Text("No Menu available").foregroundColor(colorScheme == .dark ? .init(UIColor.lightGray) : .init(UIColor.darkGray)) Spacer() } } diff --git a/Campus-iOS/MapComponent/View/Cafeterias/MealPlanView.swift b/Campus-iOS/MapComponent/View/Cafeterias/MealPlanView.swift index b7b3a6a9..8ee29fc5 100644 --- a/Campus-iOS/MapComponent/View/Cafeterias/MealPlanView.swift +++ b/Campus-iOS/MapComponent/View/Cafeterias/MealPlanView.swift @@ -12,7 +12,8 @@ struct MealPlanView: View { @Environment(\.colorScheme) var colorScheme let menus: [Menu] let cafeteria: Cafeteria - @State var selectedMenu: Menu + @State var +selectedMenu: Menu var body: some View { VStack { @@ -43,17 +44,8 @@ struct MealPlanView: View { } } .padding(.horizontal, 5.0) - - MenuView(menu: selectedMenu) - /* - if let menu = selectedMenu { - - } else { - Spacer().frame(height: 20) - Text("No Menu available today").foregroundColor(colorScheme == .dark ? .init(UIColor.lightGray) : .init(UIColor.darkGray)) - } - */ + MenuScreen(menu: selectedMenu) } } else { Text("No Menus available") diff --git a/Campus-iOS/MapComponent/View/Cafeterias/MenuView.swift b/Campus-iOS/MapComponent/View/Cafeterias/MenuView.swift index 14de8d69..b8b6c3c9 100644 --- a/Campus-iOS/MapComponent/View/Cafeterias/MenuView.swift +++ b/Campus-iOS/MapComponent/View/Cafeterias/MenuView.swift @@ -7,15 +7,62 @@ import SwiftUI +class MenuViewModel: ObservableObject { + @Published var state: APIState<[String: DishLabel]> = .na + + let service = DishService() + + func getDishLabels(forcedRefresh: Bool = false) async { + if !forcedRefresh { + self.state = .loading + } + + let dishLabels = await service.fetch(forcedRefresh: forcedRefresh) + if let generalLabels = dishLabels { + self.state = .success( + data: generalLabels + ) + } else { + self.state = .success(data: [:]) + } + } +} + +struct MenuScreen: View { + @StateObject var vm: MenuViewModel = MenuViewModel() + let menu: Menu + + var body: some View { + Group { + switch vm.state { + case .success(let dishLabels): + MenuView(menu: menu, dishLabels: dishLabels) + case .loading, .na: + LoadingView(text: "Fetching Menus") + case .failed(_): + VStack { + Spacer() + // Since some cafeterias do not update their menus this is how we handle error here. There could be a better differentiation. + Text("No Menu available") + Spacer() + } + } + }.task { + await vm.getDishLabels() + } + } +} + struct MenuView: View { let menu: Menu + let dishLabels: [String : DishLabel] var body: some View { List { ForEach(menu.categories.sorted { $0.name < $1.name }) { category in Section(category.name) { ForEach(category.dishes, id: \.self) { dish in - DishView(dish: dish) + DishView(dish: dish, dishLabels: dishLabels) } } } diff --git a/Campus-iOS/MapComponent/View/StudyRooms/StudyRoomDetailsScreen.swift b/Campus-iOS/MapComponent/View/StudyRooms/StudyRoomDetailsScreen.swift index aaa980ee..041e960a 100644 --- a/Campus-iOS/MapComponent/View/StudyRooms/StudyRoomDetailsScreen.swift +++ b/Campus-iOS/MapComponent/View/StudyRooms/StudyRoomDetailsScreen.swift @@ -47,6 +47,9 @@ struct StudyRoomDetailsScreen: View { .padding() } } + }.task { + /// Cache currently not working since the view permanently redraws, due to an unknonw reason, i.e. the loading screen and the loaded data is flickering, when forcedRefresh is removed from the method call (i.e. forcedRefresh is then false since its default value is false, see getRoomImageMappin() inside the view model). + await vm.getRoomImageMapping(for: self.room, forcedRefresh: true) }.alert( "Error while fetching Room Images", isPresented: $vm.hasError, diff --git a/Campus-iOS/MapComponent/View/StudyRooms/StudyRoomDetailsView.swift b/Campus-iOS/MapComponent/View/StudyRooms/StudyRoomDetailsView.swift index a62c5eec..03ee555f 100644 --- a/Campus-iOS/MapComponent/View/StudyRooms/StudyRoomDetailsView.swift +++ b/Campus-iOS/MapComponent/View/StudyRooms/StudyRoomDetailsView.swift @@ -67,69 +67,3 @@ struct StudyRoomDetailsView: View { }.padding([.trailing], 15) } } - -//struct StudyRoomDetailsView: View { -// -// @ObservedObject var viewModel: StudyRoomViewModel -// -// @State var showPopup = false -// -// init(studyRoom room: StudyRoom) { -// self.viewModel = StudyRoomViewModel(studyRoom: room) -// } -// -// func printCell(key: String, value: String?) -> some View { -// if let val = value { -// return AnyView(HStack { -// Text(key) -// .foregroundColor(Color(UIColor.darkGray)) -// Spacer() -// Text(val).foregroundColor(.gray) -// }) -// } -// -// return AnyView(EmptyView()) -// } -// -// var body: some View { -// VStack(alignment: .leading) { -// Spacer() -// if viewModel.roomImageMapping.count > 0 { -// HStack { -// Image(systemName: "map.fill").foregroundColor(.blue) -// Text("Available Maps") -// .fontWeight(.bold) -// .font(.headline) -// } -//// MapImagesHorizontalScrollingView(viewModel: viewModel) -// Spacer(minLength: 10) -// } -// -// printCell(key: "Building:", value: viewModel.room.buildingName) -// printCell(key: "Building Number:", value: String(viewModel.room.buildingNumber)) -// printCell(key: "Building Code:", value: viewModel.room.buildingCode) -// if let id = viewModel.room.raum_nr_architekt { -// printCell(key: "ID:", value: id) -// } -// if let attributes = viewModel.room.attributes, attributes.count > 0 { -// Text("Attributes:") -// .foregroundColor(Color(UIColor.darkGray)) -// ForEach(attributes, id: \.name) { attribute in -// HStack { -// Spacer() -// Text("\(attribute.name ?? "") \(attribute.detail ?? "")") -// .foregroundColor(.gray) -// Spacer() -// } -// } -// } -// Spacer() -// }.padding([.trailing], 15) -// } -//} -// -//struct StudyRoomDetailsView_Previews: PreviewProvider { -// static var previews: some View { -// StudyRoomDetailsView(studyRoom: StudyRoom()) -// } -//} diff --git a/Campus-iOS/MapComponent/View/StudyRooms/StudyRoomGroupView.swift b/Campus-iOS/MapComponent/View/StudyRooms/StudyRoomGroupView.swift index 64e56c99..5caaf970 100644 --- a/Campus-iOS/MapComponent/View/StudyRooms/StudyRoomGroupView.swift +++ b/Campus-iOS/MapComponent/View/StudyRooms/StudyRoomGroupView.swift @@ -86,20 +86,9 @@ struct StudyRoomGroupView: View { HStack { Spacer() - - Button(action: { - let latitude = group.coordinate?.latitude - let longitude = group.coordinate?.longitude - let url = URL(string: "maps://?saddr=&daddr=\(latitude!),\(longitude!)") - - if UIApplication.shared.canOpenURL(url!) { - UIApplication.shared.open(url!, options: [:], completionHandler: nil) - } - }, label: { + GroupDirectionsButton(group: group) { Text("Show Directions \(Image(systemName: "arrow.right.circle"))") - .foregroundColor(.blue) - .font(.footnote) - }) + } } .onTapGesture { } .gesture(panelDragGesture) @@ -114,37 +103,7 @@ struct StudyRoomGroupView: View { List { ForEach(self.sortedRooms, id: \.id) { room in - DisclosureGroup(content: { - StudyRoomDetailsScreen(room: room) - }, label: { - AnyView( - HStack { - VStack(alignment: .leading) { - Text(room.name ?? "") - .fontWeight(.bold) - HStack { - Image(systemName: "barcode.viewfinder") - .frame(width: 12, height: 12) - .foregroundColor(Color("tumBlue")) - Text(room.code ?? "") - .font(.system(size: 12)) - Spacer() - } - .frame(minWidth: 0, maxWidth: .infinity) - .foregroundColor(.init(.darkGray)) - .padding(.leading, 5) - .padding(.trailing, 5) - .padding(.top, 0) - .padding(.bottom, 0) - } - - Spacer() - - room.localizedStatusText - } - ) - }) - .accentColor(Color(UIColor.lightGray)) + StudyRoomCell(room: room) } } .listStyle(.plain) @@ -190,6 +149,71 @@ struct StudyRoomGroupView: View { } } +struct GroupDirectionsButton: View { + let group: StudyRoomGroup + let content: Content + + init(group: StudyRoomGroup, @ViewBuilder content: () -> Content) { + self.group = group + self.content = content() + } + + var body: some View { + Button { + let latitude = group.coordinate?.latitude + let longitude = group.coordinate?.longitude + let url = URL(string: "maps://?saddr=&daddr=\(latitude!),\(longitude!)") + + if UIApplication.shared.canOpenURL(url!) { + UIApplication.shared.open(url!, options: [:], completionHandler: nil) + } + } label: { + self.content + .foregroundColor(.blue) + .font(.footnote) + } + } +} + +struct StudyRoomCell: View { + + let room: StudyRoom + + var body: some View { + DisclosureGroup(content: { + StudyRoomDetailsScreen(room: room) + }, label: { + AnyView( + HStack { + VStack(alignment: .leading) { + Text(room.name ?? "") + .fontWeight(.bold) + HStack { + Image(systemName: "barcode.viewfinder") + .frame(width: 12, height: 12) + .foregroundColor(Color("tumBlue")) + Text(room.code ?? "") + .font(.system(size: 12)) + Spacer() + } + .frame(minWidth: 0, maxWidth: .infinity) + .foregroundColor(.init(.darkGray)) + .padding(.leading, 5) + .padding(.trailing, 5) + .padding(.top, 0) + .padding(.bottom, 0) + } + + Spacer() + + room.localizedStatusText + } + ) + }) + .accentColor(Color(UIColor.lightGray)) + } +} + struct StudyRoomGroupView_Previews: PreviewProvider { @State static var ph: CGFloat = 0.0 static var vm = MapViewModel(cafeteriaService: CafeteriasService(), studyRoomsService: StudyRoomsService(), mock: true) diff --git a/Campus-iOS/MapComponent/ViewModel/DishViewModel.swift b/Campus-iOS/MapComponent/ViewModel/DishViewModel.swift index a4cc6691..dc00d187 100644 --- a/Campus-iOS/MapComponent/ViewModel/DishViewModel.swift +++ b/Campus-iOS/MapComponent/ViewModel/DishViewModel.swift @@ -9,29 +9,6 @@ import Foundation @MainActor class DishViewModel: ObservableObject { - @Published var state: APIState<[String: DishLabel]> = .na - - let service = DishService() - let dish: Dish - - init(dish: Dish) { - self.dish = dish - } - - func getDishLabels(forcedRefresh: Bool = false) async { - if !forcedRefresh { - self.state = .loading - } - - let dishLabels = await service.fetch(forcedRefresh: forcedRefresh) - if let generalLabels = dishLabels { - self.state = .success( - data: generalLabels - ) - } else { - self.state = .success(data: [:]) - } - } func formatPrice(dish: Dish, pricingGroup: String) -> String { let priceFormatter: NumberFormatter = { diff --git a/Campus-iOS/MapComponent/ViewModel/MapViewModel.swift b/Campus-iOS/MapComponent/ViewModel/MapViewModel.swift index f2f4e3c1..a70af53e 100644 --- a/Campus-iOS/MapComponent/ViewModel/MapViewModel.swift +++ b/Campus-iOS/MapComponent/ViewModel/MapViewModel.swift @@ -44,7 +44,7 @@ class MapViewModel: MapViewModelProtocol { var cafeterias: [Cafeteria] { get { if mock { - return mockCafeterias + return Cafeteria.previewData } else { guard case .success(let cafeterias) = self.cafeteriasState else { return [] diff --git a/Campus-iOS/MapComponent/ViewModel/StudyRoomViewModel.swift b/Campus-iOS/MapComponent/ViewModel/StudyRoomViewModel.swift index 545426d9..446c76a4 100644 --- a/Campus-iOS/MapComponent/ViewModel/StudyRoomViewModel.swift +++ b/Campus-iOS/MapComponent/ViewModel/StudyRoomViewModel.swift @@ -43,30 +43,3 @@ class StudyRoomViewModel: ObservableObject { } } } - -//final class StudyRoomViewModel: ObservableObject { -// @Published var roomImageMapping = [RoomImageMapping]() -// @Published var room: StudyRoom -// -// private let endpoint: TUMCabeAPI -// private let sessionManager = Session.defaultSession -// -// init(studyRoom room: StudyRoom) { -// self.room = room -// self.endpoint = TUMCabeAPI.roomMaps(room: String(room.raum_nr_architekt ?? "")) -// fetchImageMapping() -// } -// -// func fetchImageMapping() { -// sessionManager.request(endpoint).responseDecodable(of: [RoomImageMapping].self, decoder: JSONDecoder()) { [self] response in -// guard let mapping = response.value else { -// return -// } -// self.roomImageMapping = mapping -// } -// } -// -// func getImageURL(imageMappingId: Int) -> URL? { -// return TUMCabeAPI.mapImage(room: String(self.room.raum_nr_architekt ?? ""), id: imageMappingId).urlRequest?.url -// } -//} diff --git a/Campus-iOS/Model/Model.swift b/Campus-iOS/Model/Model.swift index 9d578de8..a2fc8f37 100644 --- a/Campus-iOS/Model/Model.swift +++ b/Campus-iOS/Model/Model.swift @@ -37,7 +37,6 @@ public class Model: ObservableObject { @Published var loginController = AuthenticationHandler() @Published var isUserAuthenticated = false -// @Published var profile: ProfileViewModel = ProfileViewModel() var anyCancellables: [AnyCancellable] = [] @@ -60,3 +59,10 @@ public class Model: ObservableObject { } } } + +public class Model_Preview: Model { + override init() { + super.init() + loginController = AuthenticationHandler_Preview() + } +} diff --git a/Campus-iOS/MoviesComponent/Model/Movie+PreviewData.swift b/Campus-iOS/MoviesComponent/Model/Movie+PreviewData.swift new file mode 100644 index 00000000..8dc3f920 --- /dev/null +++ b/Campus-iOS/MoviesComponent/Model/Movie+PreviewData.swift @@ -0,0 +1,28 @@ +// +// Movie+PreviewData.swift +// Campus-iOS +// +// Created by David Lin on 05.05.23. +// + +import Foundation + +extension Movie: Identifiable { + static let dummyData: Movie = .init( + id: 123, + actors: "Morgan Freeman", + cover: URL(string:"https://www.google.com"), + created: Date.now, + date: Date.now, + director: "Frank Darapant", + genre: "Crime", + link: URL(string:"https://www.google.com"), + movieDescription: "Yes", + rating: "11/10", + runtime: "194", + title: "Shawshank Redemption", + year: "2020" + ) + + static let previewData: [Movie] = [dummyData] +} diff --git a/Campus-iOS/MoviesComponent/ViewModel/Movie.swift b/Campus-iOS/MoviesComponent/Model/Movie.swift similarity index 90% rename from Campus-iOS/MoviesComponent/ViewModel/Movie.swift rename to Campus-iOS/MoviesComponent/Model/Movie.swift index f6c5c0cd..41484261 100644 --- a/Campus-iOS/MoviesComponent/ViewModel/Movie.swift +++ b/Campus-iOS/MoviesComponent/Model/Movie.swift @@ -7,7 +7,12 @@ import Foundation -struct Movie: Decodable { +struct Movie: Decodable, Searchable { + var comparisonTokens: [ComparisonToken] { + return [ComparisonToken(value: title ?? ""), + ComparisonToken(value: genre ?? "")] + + } var id: Int64 var actors: String? @@ -71,7 +76,7 @@ struct Movie: Decodable { self.movieDescription = movieDescription self.rating = rating self.runtime = runtime - self.title = String(title.split(separator: ":")[1].dropFirst()) + self.title = title self.year = year } @@ -116,20 +121,3 @@ struct Movie: Decodable { self.year = year } } -extension Movie: Identifiable { - static let dummyData: Movie = .init( - id: 123, - actors: "Morgan Freeman", - cover: URL(string:"https://www.google.com"), - created: Date.now, - date: Date.now, - director: "Frank Darapant", - genre: "Crime", - link: URL(string:"www.google.com"), - movieDescription: "Yes", - rating: "11/10", - runtime: "194", - title: "Shawshank Redemption", - year: "2020" - ) -} diff --git a/Campus-iOS/MoviesComponent/Screen/MovieScreen.swift b/Campus-iOS/MoviesComponent/Screen/MovieScreen.swift new file mode 100644 index 00000000..5c079aed --- /dev/null +++ b/Campus-iOS/MoviesComponent/Screen/MovieScreen.swift @@ -0,0 +1,54 @@ +// +// MovieScreen.swift +// Campus-iOS +// +// Created by David Lin on 22.01.23. +// + +import SwiftUI + +struct MovieScreen: View { + @StateObject var vm = MovieViewModel() + + var body: some View { + Group { + switch vm.state { + case .success(let movies): + VStack { + MovieView(movies: movies) + .refreshable { + await vm.getMovies(forcedRefresh: true) + } + } + case .loading, .na: + LoadingView(text: "Fetching Movies") + case .failed(let error): + FailedView( + errorDescription: error.localizedDescription, + retryClosure: vm.getMovies + ) + } + }.task { + await vm.getMovies() + }.alert( + "Error while fetching Movies", + isPresented: $vm.hasError, + presenting: vm.state) { detail in + Button("Retry") { + Task { + await vm.getMovies(forcedRefresh: true) + } + } + + Button("Cancel", role: .cancel) { } + } message: { detail in + if case let .failed(error) = detail { + if let apiError = error as? TUMCabeAPIError { + Text(apiError.errorDescription ?? "TUMCabeAPI Error") + } else { + Text(error.localizedDescription) + } + } + } + } +} diff --git a/Campus-iOS/MoviesComponent/Screen/MoviesScreen.swift b/Campus-iOS/MoviesComponent/Screen/MoviesScreen.swift deleted file mode 100644 index 22c8cd37..00000000 --- a/Campus-iOS/MoviesComponent/Screen/MoviesScreen.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// MovieScreen.swift -// Campus-iOS -// -// Created by David Lin on 22.01.23. -// - -import Foundation diff --git a/Campus-iOS/MoviesComponent/Service/MovieService.swift b/Campus-iOS/MoviesComponent/Service/MovieService.swift index 55eddf59..9e1c8e1e 100644 --- a/Campus-iOS/MoviesComponent/Service/MovieService.swift +++ b/Campus-iOS/MoviesComponent/Service/MovieService.swift @@ -2,12 +2,16 @@ // MovieService.swift // Campus-iOS // -// Created by David Lin on 22.01.23. +// Created by David Lin on 14.01.23. // import Foundation -struct MoviesService: ServiceProtocol { +protocol MovieServiceProtocol { + func fetch(forcedRefresh: Bool) async throws -> [Movie] +} + +struct MovieService: ServiceProtocol, MovieServiceProtocol { func fetch(forcedRefresh: Bool = false) async throws -> [Movie] { let response: [Movie] = try await MainAPI.makeRequest(endpoint: TUMCabeAPI.movie, forcedRefresh: forcedRefresh) diff --git a/Campus-iOS/MoviesComponent/ViewModel/MovieViewModel.swift b/Campus-iOS/MoviesComponent/ViewModel/MovieViewModel.swift new file mode 100644 index 00000000..14b37347 --- /dev/null +++ b/Campus-iOS/MoviesComponent/ViewModel/MovieViewModel.swift @@ -0,0 +1,55 @@ +// +// MoviesViewModel.swift +// Campus-iOS +// +// Created by Milen Vitanov on 27.01.22. +// + +import Foundation +import Alamofire +import FirebaseCrashlytics + +@MainActor +class MovieViewModel: ObservableObject { + @Published var state: APIState<[Movie]> = .na + @Published var hasError: Bool = false + + let service: MovieService = MovieService() + + func getMovies(forcedRefresh: Bool = false) async { + if !forcedRefresh { + self.state = .loading + } + self.hasError = false + + do { + let movies = try await service.fetch(forcedRefresh: forcedRefresh) + + self.state = .success( + data: filterAndSort(for: movies) + ) + } catch { + self.state = .failed(error: error) + self.hasError = true + } + } + + func filterAndSort(for movies: [Movie]) -> [Movie] { + let relevantMovies = movies.filter ({ + if let date = $0.date { + return Date.now <= date + } else { + // If no date available keep movie just in case + return true; + } + }) + + return relevantMovies.sorted(by: { + guard let dateOne = $0.date, let dateTwo = $1.date else { + return false + } + return dateOne < dateTwo + }) + + } +} diff --git a/Campus-iOS/MoviesComponent/Views/MovieView.swift b/Campus-iOS/MoviesComponent/Views/MovieView.swift new file mode 100644 index 00000000..306505e6 --- /dev/null +++ b/Campus-iOS/MoviesComponent/Views/MovieView.swift @@ -0,0 +1,45 @@ +// +// MoviesView.swift +// Campus-iOS +// +// Created by Milen Vitanov on 27.01.22. +// + +import SwiftUI + +struct MovieView: View { + let movies: [Movie] + @State private var selectedMovie: Movie? = nil + + var items: [GridItem] { + Array(repeating: .init(.adaptive(minimum: 120)), count: 2) + } + + var body: some View { + ZStack { + Text("No more movies this semester 😢\nGet excited for the next season!") + .foregroundColor(Color(UIColor.lightGray)) + ScrollView(.vertical) { + LazyVGrid(columns: items, spacing: 10) { + ForEach(self.movies, id: \.id ) { movie in + MovieCard(movie: movie).padding(7) + .onTapGesture { + selectedMovie = movie + } + } + .sheet(item: $selectedMovie) { movie in + MovieDetailedView(movie: movie) + } + } + .padding(10) + .background(Color.systemsBackground) + } + } + } +} + +//struct MoviesView_Previews: PreviewProvider { +// static var previews: some View { +// MoviesView() +// } +//} diff --git a/Campus-iOS/MoviesComponent/Views/MoviesView.swift b/Campus-iOS/MoviesComponent/Views/MoviesView.swift deleted file mode 100644 index b9fc19f8..00000000 --- a/Campus-iOS/MoviesComponent/Views/MoviesView.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// MoviesView.swift -// Campus-iOS -// -// Created by Milen Vitanov on 27.01.22. -// - -import SwiftUI - -struct MoviesScreen: View { - @StateObject var vm = MoviesViewModel() - - var body: some View { - Group { - switch vm.state { - case .success(let movies): - VStack { - MoviesView(movies: movies) - .refreshable { - await vm.getMovies(forcedRefresh: true) - } - } - case .loading, .na: - LoadingView(text: "Fetching News") - case .failed(let error): - FailedView( - errorDescription: error.localizedDescription, - retryClosure: vm.getMovies - ) - } - }.task { - await vm.getMovies() - }.alert( - "Error while fetching News", - isPresented: $vm.hasError, - presenting: vm.state) { detail in - Button("Retry") { - Task { - await vm.getMovies(forcedRefresh: true) - } - } - - Button("Cancel", role: .cancel) { } - } message: { detail in - if case let .failed(error) = detail { - if let apiError = error as? TUMCabeAPIError { - Text(apiError.errorDescription ?? "TUMCabeAPI Error") - } else { - Text(error.localizedDescription) - } - } - } - } -} - -struct MoviesView: View { - let movies: [Movie] - @State private var selectedMovie: Movie? = nil - - var items: [GridItem] { - Array(repeating: .init(.adaptive(minimum: 120)), count: 2) - } - - var body: some View { - ZStack { - Text("No more movies this semester 😢\nGet excited for the next season!") - .foregroundColor(Color(UIColor.lightGray)) - ScrollView(.vertical) { - LazyVGrid(columns: items, spacing: 10) { - ForEach(self.movies, id: \.id ) { movie in - MovieCard(movie: movie).padding(7) - .onTapGesture { - selectedMovie = movie - } - } - .sheet(item: $selectedMovie) { movie in - MovieDetailedView(movie: movie) - } - } - .padding(10) - .background(Color.systemsBackground) - } - } - } -} - -//struct MoviesView_Previews: PreviewProvider { -// static var previews: some View { -// MoviesView() -// } -//} diff --git a/Campus-iOS/NewsComponent/Model/News+PreviewData.swift b/Campus-iOS/NewsComponent/Model/News+PreviewData.swift new file mode 100644 index 00000000..0b1ae3fa --- /dev/null +++ b/Campus-iOS/NewsComponent/Model/News+PreviewData.swift @@ -0,0 +1,16 @@ +// +// News+PreviewData.swift +// Campus-iOS +// +// Created by David Lin on 05.05.23. +// + +import Foundation + +extension News { + static let previewData: [News] = [ + News(id: "test", sourceId: 1, date: Date.now, created: Date.now, title: "Testing news 1", link: URL(string: "https://www.tum.de"), imageURL: nil), + News(id: "test1", sourceId: 1, date: Date.now, created: Date.now, title: "Testing news 2", link: URL(string: "https://www.moodle.tum.de"), imageURL: nil), + News(id: "test2", sourceId: 1, date: Date.now, created: Date.now, title: "Testing news 3", link: URL(string: "https://www.live.rgb.tum.de"), imageURL: nil) + ] +} diff --git a/Campus-iOS/NewsComponent/Model/News.swift b/Campus-iOS/NewsComponent/Model/News.swift index 80e4b1bc..6fac5b96 100644 --- a/Campus-iOS/NewsComponent/Model/News.swift +++ b/Campus-iOS/NewsComponent/Model/News.swift @@ -7,7 +7,11 @@ import Foundation -struct News: Decodable { +struct News: Decodable, Searchable { + var comparisonTokens: [ComparisonToken] { + return [ComparisonToken(value: title ?? "")] + } + var id: String? var sourceID: Int64 var date: Date? diff --git a/Campus-iOS/NewsComponent/Model/NewsSource.swift b/Campus-iOS/NewsComponent/Model/NewsSource.swift index 9a719214..1d69ac25 100644 --- a/Campus-iOS/NewsComponent/Model/NewsSource.swift +++ b/Campus-iOS/NewsComponent/Model/NewsSource.swift @@ -9,13 +9,20 @@ import Alamofire import Combine import FirebaseCrashlytics -struct NewsSource: Decodable, Identifiable { - - public var id: Int64? - public var title: String? - public var icon: URL? - public var news: [News] - +struct NewsSource: Decodable, Identifiable, Searchable { + static func == (lhs: NewsSource, rhs: NewsSource) -> Bool { + return lhs.id == rhs.id + } + + var comparisonTokens: [ComparisonToken] { + return [ComparisonToken(value: title ?? "")] + news.flatMap({$0.comparisonTokens}) + } + + var id: Int64? + var title: String? + var icon: URL? + var news: [News] + enum CodingKeys: String, CodingKey { case id = "source" case title = "title" @@ -31,7 +38,6 @@ struct NewsSource: Decodable, Identifiable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let idString = try container.decode(String.self, forKey: .id) guard let id = Int64(idString) else { throw DecodingError.typeMismatch(Int64.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Value for id could not be converted to Int64")) @@ -46,3 +52,9 @@ struct NewsSource: Decodable, Identifiable { self.news = [] } } + +extension NewsSource { + static let previewData: [NewsSource] = [ + NewsSource(id: 12, title: "TUMNews", icon: nil, news: News.previewData) + ] +} diff --git a/Campus-iOS/NewsComponent/Screen/NewsScreen.swift b/Campus-iOS/NewsComponent/Screen/NewsScreen.swift new file mode 100644 index 00000000..0b588370 --- /dev/null +++ b/Campus-iOS/NewsComponent/Screen/NewsScreen.swift @@ -0,0 +1,53 @@ +// +// NewsScreen.swift +// Campus-iOS +// +// Created by David Lin on 22.01.23. +// + +import SwiftUI + +struct NewsScreen: View { + @StateObject var vm = NewsViewModel() + + var body: some View { + Group { + switch vm.state { + case .success(let newsSources): + VStack { + NewsView(latestFiveNews: vm.latestFiveNews, newsSources: newsSources) .refreshable { + await vm.getNewsSources(forcedRefresh: true) + } + } + case .loading, .na: + LoadingView(text: "Fetching News") + case .failed(let error): + FailedView( + errorDescription: error.localizedDescription, + retryClosure: vm.getNewsSources + ) + } + }.task { + await vm.getNewsSources() + }.alert( + "Error while fetching News", + isPresented: $vm.hasError, + presenting: vm.state) { detail in + Button("Retry") { + Task { + await vm.getNewsSources(forcedRefresh: true) + } + } + + Button("Cancel", role: .cancel) { } + } message: { detail in + if case let .failed(error) = detail { + if let apiError = error as? TUMCabeAPIError { + Text(apiError.errorDescription ?? "TUMCabeAPI Error") + } else { + Text(error.localizedDescription) + } + } + } + } +} diff --git a/Campus-iOS/NewsComponent/Service/NewsService.swift b/Campus-iOS/NewsComponent/Service/NewsService.swift index f334543c..8f59a5c8 100644 --- a/Campus-iOS/NewsComponent/Service/NewsService.swift +++ b/Campus-iOS/NewsComponent/Service/NewsService.swift @@ -2,12 +2,18 @@ // NewsService.swift // Campus-iOS // -// Created by David Lin on 22.01.23. +// Created by David Lin on 13.01.23. // import Foundation -struct NewsService: ServiceProtocol { +protocol NewsServiceProtocol { + func fetch(forcedRefresh: Bool) async throws -> [NewsSource] + + func fetch(forcedRefresh: Bool, source: String) async throws -> [News] +} + +struct NewsService: ServiceProtocol, NewsServiceProtocol { func fetch(forcedRefresh: Bool = false) async throws -> [NewsSource] { var newsSourceResponse: [NewsSource] = try await MainAPI.makeRequest(endpoint: TUMCabeAPI.newsSources, forcedRefresh: forcedRefresh) @@ -24,4 +30,10 @@ struct NewsService: ServiceProtocol { return newsSourceResponse } + + func fetch(forcedRefresh: Bool, source: String) async throws -> [News] { + let news: [News] = try await MainAPI.makeRequest(endpoint: TUMCabeAPI.news(source: "1")) + + return news + } } diff --git a/Campus-iOS/NewsComponent/ViewModel/NewsViewModel.swift b/Campus-iOS/NewsComponent/ViewModel/NewsViewModel.swift index bcfc7707..35d623ac 100644 --- a/Campus-iOS/NewsComponent/ViewModel/NewsViewModel.swift +++ b/Campus-iOS/NewsComponent/ViewModel/NewsViewModel.swift @@ -54,109 +54,3 @@ class NewsViewModel: ObservableObject { return latestFiveNews } } - - -//@MainActor -//class NewsViewModel: ObservableObject { -// -// @Published var newsSources = [NewsSource]() -// @Published var news = [News]() -// @Published var sourcesAndNews = [(Int64?, [News])]() -// //@Published var news = [News]() -// -// private let sessionManager: Session = Session.defaultSession -// -// init() { -// // TODO: Get from cache, if not found, then fetch -// -// fetch() -//// fetchNews(sourceId: 1) -// } -// -// var latestFiveNews: [(String?, News?)] { -// print(">> latestFiveNews loaded") -// let latestNews = Array(self.newsSources -// .map({$0.news}) -// .reduce([], +) -// .filter({$0.created != nil && $0.sourceID != 2}) -// .sorted(by: { -// guard let date1 = $0.created, let date2 = $1.created else { -// return false -// } -// return date1.compare(date2) == .orderedDescending -// }).prefix(5)) -// -// let latestFiveNews = latestNews.map { news in -// (newsSources.first(where: {$0.id == news.sourceID})?.title, news) -// } -// -// return latestFiveNews -// } -// -// func fetch() { -// typealias ImporterType = Importer -// -// let endpoint: URLRequestConvertible = TUMCabeAPI.newsSources -// let dateDecodingStrategy: JSONDecoder.DateDecodingStrategy? = .formatted(.yyyyMMddhhmmss) -// let importer = ImporterType(endpoint: endpoint, dateDecodingStrategy: dateDecodingStrategy) -// -// importer.performFetch(handler: { result in -// switch result { -// case .success(let incoming): -// incoming.forEach { newsSource in -// self.fetchNews(sourceId: newsSource.id) -// } -// -// self.newsSources = incoming -// case .failure(let error): -// print(error) -// } -// }) -// } -// -// func fetchNews(sourceId: Int64?) { -// typealias ImporterTypeNews = Importer -// -// guard let id = sourceId else { -// print("NewsSource contains no id") -// return -// } -// -// let endpointNews: URLRequestConvertible = TUMCabeAPI.news(source: id.description) -// let dateDecodingStrategyNews: JSONDecoder.DateDecodingStrategy? = .formatted(.yyyyMMddhhmmss) -// let importerNews = ImporterTypeNews(endpoint: endpointNews, dateDecodingStrategy: dateDecodingStrategyNews) -// -// importerNews.performFetch(handler: { result in -// switch result { -// case .success(let storage): -// let news = storage.filter( { -// guard let title = $0.title, let link = $0.link else { -// return false -// } -// return !title.isEmpty && !link.description.isEmpty -// } ) -// self.sourcesAndNews.append((sourceId, news)) -// case .failure(let error): -// print(error) -// } -// }) -// } -// -// -//} - -//class MockNewsViewModel: NewsViewModel { -// -// static let mockNewsA = News(id: "1", sourceId: 1, date: Date(), created: Date(), title: "Dummy Title", link: URL(string: "https://github.com/orgs/TUM-Dev"), imageURL: "https://app.tum.de/File/news/newspread/dab04abdf3954d3e1bf56cef44d68662.jpg") -// static let mockNewsB = News(id: "3", sourceId: 3, date: Date(), created: Date(), title: "Dummy Title", link: URL(string: "https://github.com/orgs/TUM-Dev"), imageURL: "https://app.tum.de/File/news/newspread/dab04abdf3954d3e1bf56cef44d68662.jpg") -// -// static let newsSourceA = NewsSource(id: 1, title: "TUM News A", icon: nil, news: [mockNewsA, mockNewsA, mockNewsA]) -// static let newsSourceB = NewsSource(id: 3, title: "TUM News B", icon: nil, news: [mockNewsB, mockNewsB, mockNewsB]) -// -// let mockNewsSources = [newsSourceA, newsSourceB] -// -// -// override func fetch() { -// self.newsSources = mockNewsSources -// } -//} diff --git a/Campus-iOS/ProfileComponent/View/ProfileView.swift b/Campus-iOS/ProfileComponent/View/ProfileView.swift index db8157ad..e2cac22f 100644 --- a/Campus-iOS/ProfileComponent/View/ProfileView.swift +++ b/Campus-iOS/ProfileComponent/View/ProfileView.swift @@ -70,7 +70,7 @@ struct ProfileView: View { Label("News", systemImage: "newspaper") } - NavigationLink(destination: MoviesScreen() + NavigationLink(destination: MovieScreen() .navigationBarTitle(Text("Movies")) .navigationBarTitleDisplayMode(.large) ) { diff --git a/Campus-iOS/ProfileComponent/ViewModel/ProfileViewModel.swift b/Campus-iOS/ProfileComponent/ViewModel/ProfileViewModel.swift index 161e5d69..e6ad538d 100644 --- a/Campus-iOS/ProfileComponent/ViewModel/ProfileViewModel.swift +++ b/Campus-iOS/ProfileComponent/ViewModel/ProfileViewModel.swift @@ -126,136 +126,5 @@ class ProfileViewModel: ObservableObject { return nil } } - - // let imageRequest = TUMOnlineAPI.profileImage(personGroup: personGroup, id: personId) - // - // self.sessionManager.request(imageRequest).responseData(completionHandler: { response in - // if let imageData = response.value, let image = UIImage(data: imageData) { - // return Image(uiImage: image) - // } - // - // self.sessionManager.request(TUMOnlineAPI.personDetails(identNumber: obfuscatedID)).responseDecodable(of: PersonDetails.self, decoder: XMLDecoder()) { response in - // guard let image = response.value?.image else { return } - // self.profileImage = Image(uiImage: image) - // } - // }) } } - -//@MainActor -//class ProfileViewModel: ObservableObject { -// -// @Published var profile: Profile? -// @Published var tuition: Tuition? -// @Published var profileImage = Image(systemName: "person.crop.circle.fill") -// -// private let sessionManager = Session.defaultSession -// -// var profileState : ProfileState = .na -// var tuitionState : TuitionState = .na -// -// static let defaultProfile = Profile( -// firstname: nil, -// surname: "TUM Student".localized, -// tumId: "TUM ID", -// obfuscatedID: nil, -// obfuscatedIDEmployee: nil, -// obfuscatedIDExtern: nil, -// obfuscatedIDStudent: nil, -// image: nil -// ) -// -// init() { -// self.profile = Self.defaultProfile -// } -// -// init(model: Model) { -// switch model.loginController.credentials { -// case .none, .noTumID: -// self.profile = Self.defaultProfile -// case .tumID(_, _), .tumIDAndKey(_, _, _): -// fetch() -// } -// } -// -// func fetch(callback: @escaping (Result) -> Void = {_ in }) { -// let importer = Importer, XMLDecoder>(endpoint: TUMOnlineAPI.identify) -// importer.performFetch( handler: { result in -// DispatchQueue.main.async { -// switch result { -// case .success(let storage): -// self.profile = storage.rows?.first -// if let personGroup = self.profile?.personGroup, let personId = self.profile?.id, let obfuscatedID = self.profile?.obfuscatedID { -// self.downloadProfileImage(personGroup: personGroup, personId: personId, obfuscatedID: obfuscatedID) -// } -// -// self.checkTuitionFunc() -// -// if let _ = self.profile { -// callback(.success(true)) -// } else { -// callback(.failure(CampusOnlineAPI.Error.noPermission)) -// } -// case .failure(let error): -// callback(.failure(error)) -// print(error) -// } -// } -// }) -// } -// -// func downloadProfileImage(personGroup: String, personId: String, obfuscatedID: String) { -// let imageRequest = TUMOnlineAPI.profileImage(personGroup: personGroup, id: personId) -// self.sessionManager.request(imageRequest).responseData(completionHandler: { response in -// if let imageData = response.value, let image = UIImage(data: imageData) { -// self.profileImage = Image(uiImage: image) -// return -// } -// -// self.sessionManager.request(TUMOnlineAPI.personDetails(identNumber: obfuscatedID)).responseDecodable(of: PersonDetails.self, decoder: XMLDecoder()) { response in -// guard let image = response.value?.image else { return } -// self.profileImage = Image(uiImage: image) -// } -// }) -// } -// -// func checkTuitionFunc(callback: @escaping (Result) -> Void = {_ in }) { -// -// let importerTuition = Importer, -// XMLDecoder>(endpoint: TUMOnlineAPI.tuitionStatus, dateDecodingStrategy: .formatted(DateFormatter.yyyyMMdd)) -// -// DispatchQueue.main.async { -// importerTuition.performFetch(handler: { result in -// switch result { -// case .success(let storage): -// self.tuition = storage.rows?.first -// if let _ = self.tuition { -// callback(.success(true)) -// } else { -// callback(.failure(CampusOnlineAPI.Error.noPermission)) -// } -// case .failure(let error): -// callback(.failure(error)) -// print(error) -// } -// }) -// } -// -// } -//} - -//extension ProfileViewModel { -// enum ProfileState { -// case na -// case loading -// case success(data: Profile?) -// case failed(error: Error) -// } -// -// enum TuitionState { -// case na -// case loading -// case success(data: Tuition?) -// case failed(error: Error) -// } -//} diff --git a/Campus-iOS/RoomFinder/Entity/FoundRoom.swift b/Campus-iOS/RoomFinder/Entity/FoundRoom.swift index b1a0c0a1..3f292f05 100644 --- a/Campus-iOS/RoomFinder/Entity/FoundRoom.swift +++ b/Campus-iOS/RoomFinder/Entity/FoundRoom.swift @@ -29,8 +29,8 @@ struct FoundRoom: Codable, Hashable { let info: String let address: String let purpose: String - let campus: String - let name: String + let campus: String? + let name: String? enum CodingKeys: String, CodingKey { case roomId = "room_id" diff --git a/Campus-iOS/RoomFinder/Service/RoomFinderService.swift b/Campus-iOS/RoomFinder/Service/RoomFinderService.swift index 60dfbb32..6148fbd4 100644 --- a/Campus-iOS/RoomFinder/Service/RoomFinderService.swift +++ b/Campus-iOS/RoomFinder/Service/RoomFinderService.swift @@ -4,15 +4,17 @@ // // Created by Philipp Zagar on 01.01.23. // + import Foundation -import Alamofire protocol RoomFinderServiceProtocol { func search(query: String) async throws -> NavigaTumSearchResponse func details(id: String) async throws -> NavigaTumNavigationDetails + func fetch(for query: String, forcedRefresh: Bool) async throws -> [FoundRoom] } struct RoomFinderService: RoomFinderServiceProtocol { + func search(query: String) async throws -> NavigaTumSearchResponse { return try await MainAPI.makeRequest(endpoint: NavigaTUMAPI.search(query: query)) } @@ -22,4 +24,10 @@ struct RoomFinderService: RoomFinderServiceProtocol { return try await MainAPI.makeRequest(endpoint: NavigaTUMAPI.details(id: id, language: language)) } + + func fetch(for query: String, forcedRefresh: Bool) async throws -> [FoundRoom] { + let response : [FoundRoom] = try await MainAPI.makeRequest(endpoint: TUMCabeAPI.roomSearch(query: query)) + + return response + } } diff --git a/Campus-iOS/RoomFinder/ViewModel/RoomFinderViewModel.swift b/Campus-iOS/RoomFinder/ViewModel/RoomFinderViewModel.swift index 0fd51698..0a006727 100644 --- a/Campus-iOS/RoomFinder/ViewModel/RoomFinderViewModel.swift +++ b/Campus-iOS/RoomFinder/ViewModel/RoomFinderViewModel.swift @@ -14,36 +14,18 @@ class RoomFinderViewModel: ObservableObject { @Published var result: [FoundRoom] = [] @Published var errorMessage: String = "" - func fetch(searchString: String) async { - guard !searchString.isEmpty else { + func fetch(for query: String, forcedRefresh: Bool = false) async { + guard !query.isEmpty else { self.errorMessage = "" return } do { - self.result = try await MainAPI.makeRequest(endpoint: TUMCabeAPI.roomSearch(query: searchString)) + self.result = try await MainAPI.makeRequest(endpoint: TUMCabeAPI.roomSearch(query: query)) self.errorMessage = "" } catch { print(error) - self.errorMessage = NSString(format: "Unable to find room".localized as NSString, searchString) as String + self.errorMessage = NSString(format: "Unable to find room".localized as NSString, query) as String } - -// let endpoint = TUMCabeAPI.roomSearch(query: searchString) -// sessionManager.cancelAllRequests() -// let request = sessionManager.request(endpoint) -// request.responseDecodable(of: [FoundRoom].self, decoder: JSONDecoder()) { [weak self] response in -// guard !request.isCancelled else { -// // cancelAllRequests doesn't seem to cancel all requests, so better check for this explicitly -// return -// } -// -// self?.result = response.value ?? [] -// -// if let result = self?.result, result.isEmpty { -// self?.errorMessage = NSString(format: "Unable to find room".localized as NSString, searchString) as String -// } else { -// self?.errorMessage = "" -// } -// } } } diff --git a/Campus-iOS/RoomFinder/Views/RoomFinderDetailsBaseView.swift b/Campus-iOS/RoomFinder/Views/RoomFinderDetailsBaseView.swift index 02891c2a..5838ba9c 100644 --- a/Campus-iOS/RoomFinder/Views/RoomFinderDetailsBaseView.swift +++ b/Campus-iOS/RoomFinder/Views/RoomFinderDetailsBaseView.swift @@ -27,7 +27,7 @@ struct RoomFinderDetailsBaseView: View { Divider() LectureDetailsBasicInfoRowView( iconName: "building.columns", - text: "\(room.campus), \(room.buildingNumber)" + text: "\(room.campus ?? "n.a."), \(room.buildingNumber)" ) Divider() LectureDetailsBasicInfoRowView( diff --git a/Campus-iOS/RoomFinder/Views/RoomFinderListView.swift b/Campus-iOS/RoomFinder/Views/RoomFinderListView.swift index a932b84d..e4c9a781 100644 --- a/Campus-iOS/RoomFinder/Views/RoomFinderListView.swift +++ b/Campus-iOS/RoomFinder/Views/RoomFinderListView.swift @@ -16,27 +16,7 @@ struct RoomFinderListView: View { var body: some View { List { ForEach(self.viewModel.result, id: \.id) { room in - NavigationLink( - destination: RoomFinderDetailsView(room: room) - .navigationBarTitleDisplayMode(.inline) - ) { - VStack(alignment: .leading) { - HStack { - Text(room.info) - } - - Spacer() - - HStack { - Text(room.roomCode) - .foregroundColor(Color(.secondaryLabel)) - Spacer().frame(width: 5) - Text(room.purpose) - .font(.footnote) - .foregroundColor(Color(.secondaryLabel)) - } - } - } + RoomFinderListCellView(room: room) } if viewModel.errorMessage != "" { VStack { @@ -54,6 +34,37 @@ struct RoomFinderListView: View { } } +struct RoomFinderListCellView: View { + let room: FoundRoom + + var body: some View { + NavigationLink( + destination: RoomFinderDetailsView(room: room) + .navigationBarTitleDisplayMode(.inline) + ) { + HStack { + VStack(alignment: .leading) { + HStack { + Text(room.info) + } + + Spacer() + + HStack { + Text(room.roomCode) + .foregroundColor(Color(.secondaryLabel)) + Spacer().frame(width: 5) + Text(room.purpose) + .font(.footnote) + .foregroundColor(Color(.secondaryLabel)) + } + } + Spacer() + }.padding() + } + } +} + struct RoomFinderListView_Previews: PreviewProvider { static var previews: some View { RoomFinderListView(model: MockModel(), viewModel: RoomFinderViewModel()) diff --git a/Campus-iOS/RoomFinder/Views/RoomFinderView.swift b/Campus-iOS/RoomFinder/Views/RoomFinderView.swift index e365c582..476e0606 100644 --- a/Campus-iOS/RoomFinder/Views/RoomFinderView.swift +++ b/Campus-iOS/RoomFinder/Views/RoomFinderView.swift @@ -34,7 +34,7 @@ struct RoomFinderView: View { } func search(_ searchValue: String) async { - await self.viewModel.fetch(searchString: searchValue) + await self.viewModel.fetch(for: searchValue) } } diff --git a/Campus-iOS/SearchComponent/Types and Protocols/ComparisonToken.swift b/Campus-iOS/SearchComponent/Types and Protocols/ComparisonToken.swift new file mode 100644 index 00000000..61a1ae5a --- /dev/null +++ b/Campus-iOS/SearchComponent/Types and Protocols/ComparisonToken.swift @@ -0,0 +1,34 @@ +// +// ComparisonToken.swift +// Campus-iOS +// +// Created by David Lin on 27.12.22. +// + +import Foundation + +infix operator =/ + +struct ComparisonToken: Hashable { + var value: String + var type: ComparisonTokenType = .tokenized + + enum ComparisonTokenType { + case tokenized + case raw + } + + static func =/ (lhs: Self, rhs: Self) -> Bool { + guard lhs.value.count == rhs.value.count else { + return false + } + + for i in 0..) -> String { + return String(self.filter {validChars.contains($0)}) + } +} diff --git a/Campus-iOS/SearchComponent/Types and Protocols/Extensions/String+Levenshtein.swift b/Campus-iOS/SearchComponent/Types and Protocols/Extensions/String+Levenshtein.swift new file mode 100644 index 00000000..4fbc795c --- /dev/null +++ b/Campus-iOS/SearchComponent/Types and Protocols/Extensions/String+Levenshtein.swift @@ -0,0 +1,71 @@ +// +// StringExtension.swift +// Campus-iOS +// +// Created by David Lin on 27.12.22. +// + +import Foundation + +extension String { + /// Retrieve the levenshtein distance betweent `self` and the string `comparisonToken` + /// + /// ``` + /// "helo".levenshtein(to: "hello") // 1 + /// ``` + /// The levenshtein distance calculates the distance of to string, i.e. tokens. + /// This evalutes if two words are similiar even if misspelling occurs in one of the words. + /// This implementation is not considered as one of the most efficient, but one of the most clearest. + /// See the [source](https://gist.github.com/bgreenlee/52d93a1d8fa1b8c1f38b). + /// + /// - Parameters: + /// - comparisonToken: The string which is compared to `self` + /// - Returns: The levenshtein distance as integer + func levenshtein(to comparisonToken: String) -> Int { + /// Either `self `or the `comparisonToken` is empty. + if self.isEmpty && comparisonToken.isEmpty { + return 0 + } else if self.isEmpty { + return comparisonToken.count + } else if comparisonToken.isEmpty { + return self.count + } + + /// Starting with the levenshtein distance algorithm + // Create character arrays + let a = Array(self) + let b = Array(comparisonToken) + + // Initialize matrix of size |a|+1 * |b|+1 to zero + var dist = [[Int]]() + for _ in 0...a.count { + dist.append([Int](repeating: 0, count: b.count + 1)) + } + + // 'a' prefixes can be transformed into empty string by deleting every char + for i in 1...a.count { + dist[i][0] = i + } + + // 'b' prefixes can be created from empty string by inserting every char + for j in 1...b.count { + dist[0][j] = j + } + + for i in 1...a.count { + for j in 1...b.count { + if a[i-1] == b[j-1] { + dist[i][j] = dist[i-1][j-1] // Noop + } else { + dist[i][j] = Swift.min( + dist[i-1][j] + 1, // Deletion + dist[i][j-1] + 1, // Insertion + dist[i-1][j-1] + 1 // Substitution + ) + } + } + } + + return dist[a.count][b.count] + } +} diff --git a/Campus-iOS/SearchComponent/Types and Protocols/GlobalSearch.swift b/Campus-iOS/SearchComponent/Types and Protocols/GlobalSearch.swift new file mode 100644 index 00000000..a926a487 --- /dev/null +++ b/Campus-iOS/SearchComponent/Types and Protocols/GlobalSearch.swift @@ -0,0 +1,191 @@ +// +// GlobalSearch.swift +// Campus-iOS +// +// Created by David Lin on 27.12.22. +// + +import Foundation +typealias Distances = [Int] + +enum GlobalSearch { + /// Returns an optional array of tuples in ascending order by the best search matches of a given array of a type you specify and a search `query`. + /// + /// ``` + /// struct Grades: Searchable { + /// let id = UUID() + /// let title: String + /// let examiner: String + /// let grade: String + /// let semester: String + /// + /// var comparisonTokens: [ComparisonToken] = { + /// ComparisonToken(value: title), + /// ComparisonToken(value: examiner), + /// ComparisonToken(value: grade, type: .raw), + /// ComparisonToken(value: semester) + /// } + /// } + /// + /// let grades = [ + /// Grade(title: "Grundlagen: Betriebssysteme", examiner: "Ott", grade: "2,7", semester: "21W"), + /// Grade(title: "Einführung in die Informatik", examiner: "Seidl", grade: "2,0", semester: "22W"), + /// ] + /// + /// let query = "grade ott" + /// + /// let results = tokenSearch(for: query, in: grades) // [ + /// (Grade(title: "Grundlagen: Betriebssysteme", examiner: "Ott", grade: "2,7", semester: "21W"), 0), + /// (Grade(title: "Einführung in die Informatik", examiner: "Seidl", grade: "2,0", semester: "22W"), 80) + /// ] + /// ``` + /// + /// > Warning: It can return nil if either the `query` is an empty String and/or if the `value`are an empty string of each `comparisonToken` of each `searchable`. + /// + /// - Parameters: + /// - query: A String representing the query to be searched for. + /// - searchables: An array of a type conforming to the `Searchable` protocol, which will be searched through by the `query`. + /// - Returns: An array of tuples containing a object of the specified type conforming to `Searchable` and an Integer, representing the best relative levenshtein distance. The array is of ascending order by which object of the `searchables` matches `query` the most. + static func tokenSearch(for query: String, in searchables: [T]) -> [(T, Distances)]? { + + let tokens = tokenize(query) + + var levenshteinValues = [T: Distances]() + + for token in tokens { + for searchable in searchables { + + // Retrieve the best relative levensthein value for the current token, i.e. if the token would be "kempr" the best relative levenshtein values is 16. + guard let newDistance = bestRelativeLevensthein(for: token, with: searchable) else { + break + } +// print(newDistance) + + // Add new distance to the dictionary where the seachrable is the key. + levenshteinValues[searchable, default: []].append(newDistance) + } + } + + let results = levenshteinValues + .sorted { $0.value.sorted(by: <) >> $1.value.sorted(by: <) } // Sort the `searchable` ids by the increasing order since the lowest distance is the best. + .map { levenshteinTuple in + // Map the key and values to a tuple to have the tuple labels inside the respective ViewModel. + return (levenshteinTuple.0, levenshteinTuple.1) + } + + return results + } + + /// Returns the best relative levenshtein value for a given `String` and a `Searchable`. The lower the result, the more common is the `token` to one property of the `searchable`. + /// + /// ``` + /// struct Grades: Searchable { + /// let id = UUID() + /// let title: String + /// let examiner: String + /// let grade: String + /// let semester: String + /// + /// var comparisonTokens: [ComparisonToken] = { + /// ComparisonToken(value: title), + /// ComparisonToken(value: examiner), + /// ComparisonToken(value: grade, type: .raw), + /// ComparisonToken(value: semester) + /// } + /// } + /// + /// let grade = Grade(title: "Grundlagen: Betriebssysteme", examiner: "Ott", grade: "2,7", semester: "21W") + /// + /// let relLevensheit = bestRelativeLevensthein(for: "betribsystme", searchable: grade) // 20 + /// ``` + /// It returns the relative levenshtein distance for `token` and one `comparisonToken`of the `searchable` and is calculated by the levenshtein distance between the the two strings divided by the length of the `comparisonToken` and multiplied by `100`. This requires `searchable` to conform to the `Searchable` protocol. + /// + /// > Warning: Empty `comparisonToken` will not be concidered when evaluating the best (i.e. lowest) relative levensthein distance. If all `comparisonTokens` and/or `token` are empty strings `nil` returns. + /// + /// - Parameters: + /// - token: The string to be compared to the `comparisonTokens`. + /// - searchable: The instance of an type conforming to the `Searchable` protocol, which needs to have a property `comparisonToken`. They represent the tokens of the `Searchable` which are compared to the `token`. + /// - Returns: An optional integer indicating the best (lowest) relative levenshtein distance from `token` to the `searchable`. + static func bestRelativeLevensthein(for token: String, with searchable: T) -> Int? { + guard !token.isEmpty else { + return nil + } + + // Combine all tokens of the current searchable to one array of strings with `tokenize()`. + // E.g.: ["grundlagen", "datenbanken", "kemper", "1,0", "in0008", "schriftlich", "21w", "informatik"] + // Afterwards the relative levenshtein distance is calculated between the `token` and each `comparisonToken` from the `searchable`. + + return searchable.tokenize().compactMap { dataToken in + guard dataToken.count > 0 else { + return nil + } + +// let result = Int(Double(token.levenshtein(to: dataToken))/Double(dataToken.count)*100) + let lev = token.levenshtein(to: dataToken) + + //Normalized Levenshtein Distance (see: https://ieeexplore.ieee.org/document/4160958) + let result = Double(2 * lev) / Double(1 * (token.count + dataToken.count) + lev) + //print("For token \(token) and compToken \(dataToken): \(result)") + + return Int(result * 100) + }.min() + } + + static func relativeLevensthein(for token: String, with searchable: T) -> [Int]? { + guard !token.isEmpty else { + return nil + } + + // Combine all tokens of the current searchable to one array of strings with `tokenize()`. + // E.g.: ["grundlagen", "datenbanken", "kemper", "1,0", "in0008", "schriftlich", "21w", "informatik"] + // Afterwards the relative levenshtein distance is calculated between the `token` and each `comparisonToken` from the `searchable`. + + return searchable.tokenize().compactMap { dataToken in + guard dataToken.count > 0 else { + return nil + } + + let result = Int(Double(token.levenshtein(to: dataToken))/Double(dataToken.count)*100) + + //print("For token \(token) and compToken \(dataToken): \(result)") + + return result + } + } + + + + /// Produce an array of tokens from a `query`. + /// + /// ``` + /// let tokens = tokenize("grade Grundlagen: Betriebssysteme ") // ["grade", "grundlagen", "betriebssysteme"] + /// ``` + /// + /// - Parameters: + /// - query: The string to be converted to tokens.. + /// - Returns: An array representing tokens derived from the `query`. + static func tokenize(_ query: String) -> [String] { + let updatedQuery = query.trimmingCharacters(in: .whitespaces).lowercased().folding(options: [.diacriticInsensitive], locale: .current).keep(validChars: Set("abcdefghijklmnopqrstuvwxyz 1234567890")).split(separator: " ").map({String($0)}) + + return updatedQuery + } +} + +infix operator >> + +func >>(_ lhs: [Int], _ rhs: [Int]) -> Bool { + let index = min(lhs.count, rhs.count) + for i in 0.. than the first value of rhs + return true + } else if lhs[i] > rhs[i] { // rhs is better if a e.g. all values until the 5th value are the same. The 5th value of lhs is < than the 5th value of rhs + return false + } + } + + // If lhs == [] != rhs false is returned + // If rhs == [] != lhs true is returned + // If rhs == [] == lhs true is returned + // If all values are the same, then return true if lhs has equal or more values than rhs else return false + return lhs.count >= rhs.count +} diff --git a/Campus-iOS/SearchComponent/Types and Protocols/SearchError.swift b/Campus-iOS/SearchComponent/Types and Protocols/SearchError.swift new file mode 100644 index 00000000..901a4374 --- /dev/null +++ b/Campus-iOS/SearchComponent/Types and Protocols/SearchError.swift @@ -0,0 +1,23 @@ +// +// SearchError.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import Foundation + +enum SearchError: Error, CustomStringConvertible{ + case empty(searchQuery: String) + case unexpected + + public var description: String { + switch self { + case .empty(let searchQuery): + return "No search results were found for: \"\(searchQuery)\"." + case .unexpected: + return "An unexpected error occurred." + } + } +} + diff --git a/Campus-iOS/SearchComponent/Types and Protocols/SearchState.swift b/Campus-iOS/SearchComponent/Types and Protocols/SearchState.swift new file mode 100644 index 00000000..67b4487b --- /dev/null +++ b/Campus-iOS/SearchComponent/Types and Protocols/SearchState.swift @@ -0,0 +1,15 @@ +// +// File.swift +// Campus-iOS +// +// Created by David Lin on 05.05.23. +// + +import Foundation + +enum SearchState { + case na + case loading + case success(data: [(T, Distances)]) + case failed(error: Error) +} diff --git a/Campus-iOS/SearchComponent/Types and Protocols/Searchable.swift b/Campus-iOS/SearchComponent/Types and Protocols/Searchable.swift new file mode 100644 index 00000000..03186a53 --- /dev/null +++ b/Campus-iOS/SearchComponent/Types and Protocols/Searchable.swift @@ -0,0 +1,55 @@ +// +// Searchable.swift +// Campus-iOS +// +// Created by David Lin on 27.12.22. +// + +import Foundation + +protocol Searchable: Hashable { + var comparisonTokens: [ComparisonToken] { get } + + func tokenize() -> [String] +} + +extension Searchable { + + /// Produce an array of tokens from the `comprarisonTokens` stored in `self`. + /// + /// ``` + /// struct Grades: Searchable { + /// let id = UUID() + /// let title: String + /// let examiner: String + /// let grade: String + /// let semester: String + /// + /// var comparisonTokens: [ComparisonToken] = { + /// ComparisonToken(value: title), + /// ComparisonToken(value: examiner), + /// ComparisonToken(value: grade, type: .raw), + /// ComparisonToken(value: semester) + /// } + /// } + /// + /// let grade = Grade(title: "Grundlagen: Betriebssysteme", examiner: "Ott", grade: "2,7", semester: "21W") + /// + /// let comparisonTokens = grade.tokenize() // ["grundlagen", "betriebssysteme", "ott", "2,7", semester: "21w"] + /// ``` + /// This method collects the `comparisonTokens` to one array if strings. Each `comparisonToken` is added to the array either `.raw`, i.e. the umodified `comparisonToken.value` or it is added `.tokenized`, i.e. the only lowercase letters, a single whitespace, and numbers from 0 to 9 are left. E.g. `comparisonToken.value = "Hello World! "`will be tokenized to `["hello", "world"]` and added to the returned array. + /// + /// > Important Note: This is just the standard implementation for the tokenization. If the respective type need a custom method, this can be overridden. + /// + /// - Returns: An array of Strings representing tokens. + func tokenize() -> [String] { + + return self.comparisonTokens.flatMap { comparisonToken in + if comparisonToken.type == .tokenized { + return comparisonToken.value.trimmingCharacters(in: .whitespaces).lowercased().folding(options: [.diacriticInsensitive], locale: .current).keep(validChars: Set("abcdefghijklmnopqrstuvwxyz 1234567890")).split(separator: " ").map({String($0)}) + } + + return [comparisonToken.value] + } + } +} diff --git a/Campus-iOS/SearchComponent/ViewModels/SearchResultViewModel.swift b/Campus-iOS/SearchComponent/ViewModels/SearchResultViewModel.swift new file mode 100644 index 00000000..44e33a6c --- /dev/null +++ b/Campus-iOS/SearchComponent/ViewModels/SearchResultViewModel.swift @@ -0,0 +1,81 @@ +// +// SearchResultViewModel.swift +// Campus-iOS +// +// Created by David Lin on 27.12.22. +// + +import SwiftUI +import CoreML +import NaturalLanguage + +class SearchResultViewModel: ObservableObject { + @Published var searchDataTypeResult = [(String, Double)]() + @Published var orderedTypes = [SearchResultType]() + let model: Model + + init(model: Model) { + self.model = model + } + + private lazy var dataTypeClassifierEnglish: NLModel? = { + let model = try? NLModel(mlModel: DataTypeClassifierV4English(configuration: MLModelConfiguration()).model) + return model + }() + + private lazy var dataTypeClassifierGerman: NLModel? = { + let model = try? NLModel(mlModel: DataTypeClassifierV4German(configuration: MLModelConfiguration()).model) + return model + }() + + func prepare(_ query: String) -> String { + let updatedQuery = query.trimmingCharacters(in: .whitespaces).lowercased().folding(options: [.diacriticInsensitive], locale: .current).keep(validChars: Set("abcdefghijklmnopqrstuvwxyz 1234567890?")) + + return updatedQuery + } + + func search(for query: String) { + let cleanedQuery = prepare(query) + + var language : String? + if #available(iOS 16, *) { + language = Locale.current.language.languageCode?.identifier + } else { + // Fallback on earlier versions + language = Locale.current.languageCode + } + + var modelOutput = [String:Double]() + if let language = language, language == "de" { + guard let modelOutputDE = dataTypeClassifierGerman?.predictedLabelHypotheses(for: cleanedQuery, maximumCount: 3) else { + return + } + + modelOutput = modelOutputDE + } else { + guard let modelOutputEN = dataTypeClassifierEnglish?.predictedLabelHypotheses(for: cleanedQuery, maximumCount: 3) else { + return + } + + modelOutput = modelOutputEN + } + + if modelOutput.count == 0 { + return + } + + searchDataTypeResult = modelOutput.sorted(by: {$0.value > $1.value}) + + orderedTypes = modelOutput.sorted(by: {$0.value > $1.value}).compactMap {SearchResultType(rawValue: $0.key)} + + } +} + +enum SearchResultType: String { + case Grade = "grade" + case Calendar = "calendar" + case News = "news" + case Cafeteria = "cafeteria" + case StudyRoom = "studyroom" + case Movie = "movie" +} diff --git a/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/CafeteriaSearchResultViewModel.swift b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/CafeteriaSearchResultViewModel.swift new file mode 100644 index 00000000..9ebe4300 --- /dev/null +++ b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/CafeteriaSearchResultViewModel.swift @@ -0,0 +1,40 @@ +// +// CafeteriaSearchResultViewModel.swift +// Campus-iOS +// +// Created by David Lin on 27.12.22. +// + +import Foundation + +@MainActor +class CafeteriaSearchResultViewModel: ObservableObject { + + @Published var state: SearchState = .na + @Published var hasError: Bool = false + let service: CafeteriasServiceProtocol + + init(service: CafeteriasServiceProtocol) { + self.service = service + } + + func cafeteriasSearch(for query: String, forcedRefresh: Bool = false) async { + if !forcedRefresh { + self.state = .loading + } + self.hasError = false + + do { + let data = try await service.fetch(forcedRefresh: forcedRefresh) + if let optionalResults = GlobalSearch.tokenSearch(for: query, in: data) { + self.state = .success(data: optionalResults) + } else { + self.state = .failed(error: SearchError.empty(searchQuery: query)) + } + + } catch { + self.state = .failed(error: error) + self.hasError = true + } + } +} diff --git a/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/EventSearchResultViewModel.swift b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/EventSearchResultViewModel.swift new file mode 100644 index 00000000..0dcf77c8 --- /dev/null +++ b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/EventSearchResultViewModel.swift @@ -0,0 +1,101 @@ +// +// CalendarLectureSearchResultView.swift +// Campus-iOS +// +// Created by David Lin on 28.12.22. +// + +import SwiftUI + +struct EventSearchResult: Searchable { + var comparisonTokens: [ComparisonToken] { + return lecture.comparisonTokens + events.flatMap {$0.comparisonTokens} + } + + let lecture: Lecture + let events: [CalendarEvent] +} + +@MainActor +class EventSearchResultViewModel: ObservableObject { + @Published var state: SearchState = .na + @Published var hasError: Bool = false + let lecturesService: LecturesServiceProtocol + let calendarService: CalendarServiceProtocol + let model: Model + + var token: String? { + switch self.model.loginController.credentials { + case .none, .noTumID: + return nil + case .tumID(_, let token): + return token + case .tumIDAndKey(_, let token, _): + return token + } + } + + init(model: Model, lecturesService: LecturesServiceProtocol, calendarService: CalendarServiceProtocol) { + self.model = model + self.lecturesService = lecturesService + self.calendarService = calendarService + } + + func eventsSearch(for query: String, forcedRefresh: Bool = false) async { + if !forcedRefresh { + self.state = .loading + } + self.hasError = false + + guard let token = self.token else { + self.state = .failed(error: NetworkingError.unauthorized) + self.hasError = true + return + } + + var calendarData = [CalendarEvent]() + do { + calendarData = try await calendarService.fetch(token: token, forcedRefresh: forcedRefresh) + + } catch { + print("Error fetching Calendar: \(error)") + // No error is thrown because we could have no permissons for the calendar, but for the lectures, i.e. only the lectures without calendar dates are shown + } + + do { + let lectureData = try await lecturesService.fetch(token: token, forcedRefresh: forcedRefresh) + + var eventResults = [EventSearchResult]() + if calendarData.isEmpty { + // If e.g. we do not have the permisson to read calendar, but lectures + eventResults = lectureData.map { EventSearchResult(lecture: $0, events: []) } + } else { + eventResults = lectureData.map { lecture in + let lectureEvents = calendarData.filter { currentCalendarEvent in + if let nr = currentCalendarEvent.lvNr { + if let date = currentCalendarEvent.startDate, date >= Date() { + return nr == String(lecture.id) + } else { + return false + } + } + return false + } + + return EventSearchResult(lecture: lecture, events: lectureEvents) + } + } + + if let optionalResults = GlobalSearch.tokenSearch(for: query, in: eventResults) { + self.state = .success(data: optionalResults) + } else { + self.state = .failed(error: SearchError.empty(searchQuery: query)) + self.hasError = true + } + + } catch { + self.state = .failed(error: error) + self.hasError = true + } + } +} diff --git a/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/GradeSearchResultViewModel.swift b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/GradeSearchResultViewModel.swift new file mode 100644 index 00000000..2bd011d1 --- /dev/null +++ b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/GradeSearchResultViewModel.swift @@ -0,0 +1,60 @@ +// +// GradeSearchResultViewModel.swift +// Campus-iOS +// +// Created by David Lin on 27.12.22. +// + +import SwiftUI + +@MainActor +class GradesSearchResultViewModel: ObservableObject { + @Published var state: SearchState = .na + @Published var hasError: Bool = false + + let model: Model + let service: GradesServiceProtocol + + init(model: Model, service: GradesServiceProtocol) { + self.model = model + self.service = service + } + + var token: String? { + switch self.model.loginController.credentials { + case .none, .noTumID: + return nil + case .tumID(_, let token): + return token + case .tumIDAndKey(_, let token, _): + return token + } + } + + func gradesSearch(for query: String, forcedRefresh: Bool = false) async { + if !forcedRefresh { + self.state = .loading + } + self.hasError = false + + guard let token = self.token else { + self.state = .failed(error: NetworkingError.unauthorized) + self.hasError = true + return + } + + do { + let data = try await service.fetch(token: token, forcedRefresh: forcedRefresh) + if let optionalResults = GlobalSearch.tokenSearch(for: query, in: data) { + self.state = .success(data: optionalResults) + } else { + self.state = .failed(error: SearchError.empty(searchQuery: query)) + self.hasError = true + } + + } catch { + self.state = .failed(error: error) + self.hasError = true + } + } +} diff --git a/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/LectureSearchResultViewModel.swift b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/LectureSearchResultViewModel.swift new file mode 100644 index 00000000..09ba1821 --- /dev/null +++ b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/LectureSearchResultViewModel.swift @@ -0,0 +1,51 @@ +// +// LectureSearchResultViewModel.swift +// Campus-iOS +// +// Created by David Lin on 14.01.23. +// + +import Foundation + +@MainActor +class LectureSearchResultViewModel: ObservableObject { + @Published var state: APIState<[Lecture]> = .na + @Published var hasError: Bool = false + let model : Model + + var token: String? { + switch self.model.loginController.credentials { + case .none, .noTumID: + return nil + case .tumID(_, let token): + return token + case .tumIDAndKey(_, let token, _): + return token + } + } + + init(model: Model) { + self.model = model + } + + func lectureSearch(for query: String, forcedRefresh: Bool = false) async { + if !forcedRefresh { + self.state = .loading + } + self.hasError = false + + guard let token = self.token else { + self.state = .failed(error: NetworkingError.unauthorized) + self.hasError = true + return + } + + do { + self.state = .success(data: try await LectureSearchService().fetch(for: query, token: token, forcedRefresh: forcedRefresh)) + } catch { + print("No lectures were fetched: \(String(describing: error))") + self.state = .failed(error: error) + self.hasError = true + } + } +} diff --git a/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/MovieSearchResultViewModel.swift b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/MovieSearchResultViewModel.swift new file mode 100644 index 00000000..287f0bc3 --- /dev/null +++ b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/MovieSearchResultViewModel.swift @@ -0,0 +1,47 @@ +// +// MovieSearchResultView.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import Foundation + +@MainActor +class MovieSearchResultViewModel: ObservableObject { + @Published var state: SearchState = .na + @Published var hasError: Bool = false + + let service: MovieServiceProtocol + + init(service: MovieServiceProtocol) { + self.service = service + } + + func movieSearch(for query: String, forcedRefresh: Bool = false) async { + + if !forcedRefresh { + self.state = .loading + } + self.hasError = false + + do { + let data = try await service.fetch(forcedRefresh: forcedRefresh) + let filteredMovies = data.filter { movie in + return (movie.date ?? Date.distantPast) >= Date() + } + + if let optionalResults = GlobalSearch.tokenSearch(for: query, in: filteredMovies) { + self.state = .success(data: optionalResults) + } else { + self.state = .failed(error: SearchError.empty(searchQuery: query)) + self.hasError = true + } + + } catch { + self.state = .failed(error: error) + self.hasError = true + } + } +} + diff --git a/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/NewsSearchResultViewModel.swift b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/NewsSearchResultViewModel.swift new file mode 100644 index 00000000..0f9ec3b8 --- /dev/null +++ b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/NewsSearchResultViewModel.swift @@ -0,0 +1,64 @@ +// +// NewsSearchViewModel.swift +// Campus-iOS +// +// Created by David Lin on 13.01.23. +// + +import Foundation + +@MainActor +class NewsSearchResultViewModel: ObservableObject { + ///** The following code is for all newsSources. Currently we only use TUMOnline due to lagginess ** +// @Published var state: SearchState = .na + @Published var state: SearchState = .na + @Published var hasError: Bool = false + + let service: NewsServiceProtocol + + init(service: NewsServiceProtocol) { + self.service = service + } + + func newsSearch(for query: String, forcedRefresh: Bool = false) async { + ///** The following code is for all newsSources. Currently we only use TUMOnline due to lagginess ** + +// guard let newsSources = await fetchNewsSources() else { +// return +// } +// +// var sources = [NewsSource]() +// for newsSource in newsSources { +// if let id = newsSource.id { +// if let news = await fetchNews(source: String(id)) { +// sources.append(NewsSource(id: newsSource.id, title: newsSource.title, icon: newsSource.icon, news: news)) +// print(news) +// } +// } +// } + +// if let optionalResults = GlobalSearch.tokenSearch(for: query, in: sources) { +// self.results = optionalResults +// } + + + if !forcedRefresh { + self.state = .loading + } + self.hasError = false + + do { + let data = try await service.fetch(forcedRefresh: forcedRefresh, source: "1") + if let optionalResults = GlobalSearch.tokenSearch(for: query, in: data) { + self.state = .success(data: optionalResults) + } else { + self.state = .failed(error: SearchError.empty(searchQuery: query)) + self.hasError = true + } + + } catch { + self.state = .failed(error: error) + self.hasError = true + } + } +} diff --git a/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/PersonSearchResultViewModel.swift b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/PersonSearchResultViewModel.swift new file mode 100644 index 00000000..5bb27170 --- /dev/null +++ b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/PersonSearchResultViewModel.swift @@ -0,0 +1,51 @@ +// +// PersonSearchResultViewModel.swift +// Campus-iOS +// +// Created by David Lin on 14.01.23. +// + +import Foundation + +@MainActor +class PersonSearchResultViewModel: ObservableObject { + @Published var state: APIState<[Person]> = .na + @Published var hasError: Bool = false + + let model : Model + + var token: String? { + switch self.model.loginController.credentials { + case .none, .noTumID: + return nil + case .tumID(_, let token): + return token + case .tumIDAndKey(_, let token, _): + return token + } + } + + init(model: Model) { + self.model = model + } + + func personSearch(for query: String, forcedRefresh: Bool = false) async { + if !forcedRefresh { + self.state = .loading + } + self.hasError = false + + guard let token = self.token else { + self.state = .failed(error: NetworkingError.unauthorized) + self.hasError = true + return + } + + do { + self.state = .success(data: try await PersonSearchService().fetch(for: query, token: token, forcedRefresh: forcedRefresh)) + } catch { + self.state = .failed(error: error) + self.hasError = true + } + } +} diff --git a/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/RoomFinderSearchResultViewModel.swift b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/RoomFinderSearchResultViewModel.swift new file mode 100644 index 00000000..0b7d4ead --- /dev/null +++ b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/RoomFinderSearchResultViewModel.swift @@ -0,0 +1,28 @@ +// +// RoomFinderSearchViewModel.swift +// Campus-iOS +// +// Created by David Lin on 13.01.23. +// + +import Foundation + +@MainActor +class RoomFinderSearchResultViewModel: ObservableObject { + @Published var state: APIState<[NavigaTumNavigationEntity]> = .na + @Published var hasError: Bool = false + + func roomFinderSearch(for query: String, forcedRefresh: Bool = false) async { + if !forcedRefresh { + self.state = .loading + } + self.hasError = false + + do { + self.state = .success(data: try await RoomFinderService().search(query: query).sections.flatMap(\.entries)) + } catch { + self.state = .failed(error: error) + self.hasError = true + } + } +} diff --git a/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/StudyRoomSearchResultViewModel.swift b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/StudyRoomSearchResultViewModel.swift new file mode 100644 index 00000000..a6223437 --- /dev/null +++ b/Campus-iOS/SearchComponent/ViewModels/Searchable ViewModels/StudyRoomSearchResultViewModel.swift @@ -0,0 +1,68 @@ +// +// StudyRoomSearchResultViewModel.swift +// Campus-iOS +// +// Created by David Lin on 27.12.22. +// + +import SwiftUI + +struct StudyRoomSearchResult: Searchable { + var comparisonTokens: [ComparisonToken] { + return group.comparisonTokens + rooms.flatMap {$0.comparisonTokens} + } + + let group: StudyRoomGroup + let rooms: [StudyRoom] + + static func == (lhs: StudyRoomSearchResult, rhs: StudyRoomSearchResult) -> Bool { + lhs.group.id == rhs.group.id + } +} + +@MainActor +class StudyRoomSearchResultViewModel: ObservableObject { + @Published var state: SearchState = .na + @Published var hasError = false + private let studyRoomService: StudyRoomsServiceProtocol + + init(studyRoomService: StudyRoomsServiceProtocol) { + self.studyRoomService = studyRoomService + } + + func studyRoomSearch(for query: String, forcedRefresh: Bool = false) async { + if !forcedRefresh { + self.state = .loading + } + self.hasError = false + + do { + let data = try await studyRoomService.fetch(forcedRefresh: forcedRefresh) + + guard let groups = data.groups, let rooms = data.rooms else { + self.state = .failed(error: SearchError.empty(searchQuery: query)) + self.hasError = true + return + } + + let groupRooms: [StudyRoomSearchResult] = groups.map { currentGroup in + let currentRooms = rooms.filter { + return currentGroup.rooms?.contains($0.id) ?? false + } + + return StudyRoomSearchResult(group: currentGroup, rooms: currentRooms) + } + + if let optionalResults = GlobalSearch.tokenSearch(for: query, in: groupRooms) { + self.state = .success(data: optionalResults) + } else { + self.state = .failed(error: SearchError.empty(searchQuery: query)) + self.hasError = true + } + + } catch { + self.state = .failed(error: error) + self.hasError = true + } + } +} diff --git a/Campus-iOS/SearchComponent/Views/Additional Views/SearchResultBarView.swift b/Campus-iOS/SearchComponent/Views/Additional Views/SearchResultBarView.swift new file mode 100644 index 00000000..2fbcf7f0 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/Additional Views/SearchResultBarView.swift @@ -0,0 +1,58 @@ +// +// SearchResultBar.swift +// Campus-iOS +// +// Created by David Lin on 12.02.23. +// + +import SwiftUI + +enum BarType: String { + case all = "All" + case grade = "Grades" + case calendar = "Personal Lectures" + case news = "News" + case cafeteria = "Cafeterias" + case studyRoom = "StudyRooms" + case movie = "Movies" + case roomFinder = "Room Finder" + case lectureSearch = "Lecture Search" + case personSearch = "Person Search" +} + +struct SearchResultBarView: View { + let types: [BarType] = [.all, .grade, .movie, .news, .cafeteria, .calendar, .roomFinder, .lectureSearch, .personSearch] + @Binding var selectedType: BarType + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack { + ForEach(types, id: \.rawValue) { barType in + ZStack { + if selectedType == barType { + RoundedRectangle(cornerRadius: 10) + .foregroundColor(.tumBlue) + } else { + RoundedRectangle(cornerRadius: 10) + .foregroundColor(.gray) + } + Text(barType.rawValue) + .foregroundColor(.white) + .padding([.trailing, .leading]) + }.onTapGesture { + withAnimation { + self.selectedType = barType + } + } + } + } + } + } +} + +struct SearchResultBarView_Previews: PreviewProvider { + static var previews: some View { + SearchResultBarView(selectedType: .constant(.grade)) + .frame(height: 20) + } +} diff --git a/Campus-iOS/SearchComponent/Views/Additional Views/SearchResultErrorView.swift b/Campus-iOS/SearchComponent/Views/Additional Views/SearchResultErrorView.swift new file mode 100644 index 00000000..6a895275 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/Additional Views/SearchResultErrorView.swift @@ -0,0 +1,31 @@ +// +// SearchResultErrorView.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import SwiftUI + +struct SearchResultErrorView: View { + @State var title: String + @State var error: String + + var body: some View { + ZStack { + Color.white + VStack { + Text(title) + .fontWeight(.bold) + .font(.title) + Text("Error searching: \(error)") + }.padding() + } + } +} + +struct SearchResultErrorView_Previews: PreviewProvider { + static var previews: some View { + SearchResultErrorView(title: "Grades", error: "No internet connection.") + } +} diff --git a/Campus-iOS/SearchComponent/Views/Additional Views/SearchResultLoadingView.swift b/Campus-iOS/SearchComponent/Views/Additional Views/SearchResultLoadingView.swift new file mode 100644 index 00000000..6c8c3a97 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/Additional Views/SearchResultLoadingView.swift @@ -0,0 +1,30 @@ +// +// SearchResultLoadingView.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import SwiftUI + +struct SearchResultLoadingView: View { + @State var title: String + + var body: some View { + ZStack { + Color.white + VStack { + Text(title) + .fontWeight(.bold) + .font(.title) + LoadingView(text: "Searching...") + }.padding() + } + } +} + +struct SearchResultLoadingView_Previews: PreviewProvider { + static var previews: some View { + SearchResultLoadingView(title: "Grades") + } +} diff --git a/Campus-iOS/SearchComponent/Views/Extensions and Custom Views/ExpandIcon.swift b/Campus-iOS/SearchComponent/Views/Extensions and Custom Views/ExpandIcon.swift new file mode 100644 index 00000000..01c9062c --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/Extensions and Custom Views/ExpandIcon.swift @@ -0,0 +1,36 @@ +// +// ExpandIcon.swift +// Campus-iOS +// +// Created by David Lin on 06.05.23. +// + +import SwiftUI + +struct ExpandIcon: View { + @Binding var size: ResultSize + + var body: some View { + HStack(alignment: .center) { + Spacer() + Button { + withAnimation { + switch size { + case .big: + self.size = .small + case .small: + self.size = .big + } + } + } label: { + if self.size == .small { + Image(systemName: "arrow.up.left.and.arrow.down.right") + .padding() + } else { + Image(systemName: "arrow.down.right.and.arrow.up.left") + .padding() + } + } + } + } +} diff --git a/Campus-iOS/SearchComponent/Views/Extensions and Custom Views/View+Search.swift b/Campus-iOS/SearchComponent/Views/Extensions and Custom Views/View+Search.swift new file mode 100644 index 00000000..50ebabde --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/Extensions and Custom Views/View+Search.swift @@ -0,0 +1,14 @@ +// +// View+Search.swift +// Campus-iOS +// +// Created by David Lin on 06.05.23. +// + +import SwiftUI + +extension View { + func searchStyle() -> some View { + modifier(Search()) + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultView.swift b/Campus-iOS/SearchComponent/Views/SearchResultView.swift new file mode 100644 index 00000000..03b6a80c --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultView.swift @@ -0,0 +1,215 @@ +// +// SearchResultView.swift +// Campus-iOS +// +// Created by David Lin on 27.12.22. +// + +import SwiftUI + +enum ResultSize { + case big + case small +} + +struct SearchResultView: View { + @StateObject var vm: SearchResultViewModel + @Binding var query: String + private let preview = (ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1") + @State var selectedType: BarType = BarType.all + + @State var recommondationQueries = ["Mensa Garching Menu", "StudyRoom Innenstadt free", "News TUM"] + + var body: some View { + GeometryReader { g in + if query.isEmpty { + List { + Section("Recommended Queries") { + ForEach(recommondationQueries) { query in + Button { + withAnimation { + self.query = query + } + } label: { + Text(query) + .buttonStyle(.plain) + + } + } + } + }.listStyle(.plain) + } else { + VStack { + SearchResultBarView(selectedType: $selectedType).frame(height: g.size.height/20).padding() + /// **For debugging purposes** + // Text("Your results for: \(query)") + // Spacer() + // ForEach(vm.searchDataTypeResult, id:\.0) { (key,value) in + // if let accuracy = value { + // Text("\(key) with accuracy of \(Int(accuracy*100)) %.") + // } + // } + // Spacer() + /// **For debugging purposes** + switch self.selectedType { + case .grade: + ScrollView { + Group { + if preview { + GradesSearchResultScreen(vm: GradesSearchResultViewModel(model: vm.model, service: GradesService_Preview()), query: $query, size: .big) + } else { + GradesSearchResultScreen(vm: GradesSearchResultViewModel(model: vm.model, service: GradesService()), query: $query, size: .big) + } + }.searchStyle() + } + case .cafeteria: + ScrollView { + Group { + if preview { + CafeteriaSearchResultScreen(vm: CafeteriaSearchResultViewModel(service: CafeteriasService_Preview()), query: $query, size: .big) + } else { + CafeteriaSearchResultScreen(vm: CafeteriaSearchResultViewModel(service: CafeteriasService()), query: $query, size: .big) + } + }.searchStyle() + } + case .news: + ScrollView { + Group { + if preview { + NewsSearchResultScreen(vm: NewsSearchResultViewModel(service: NewsService_Preview()), query: $query, size: .big) + } else { + NewsSearchResultScreen(vm: NewsSearchResultViewModel(service: NewsService()), query: $query, size: .big) + } + }.searchStyle() + } + + case .studyRoom: + ScrollView { + Group { + if preview { + StudyRoomSearchResultScreen(vm: StudyRoomSearchResultViewModel(studyRoomService: StudyRoomsService_Preview()), query: $query, size: .big) + } else { + StudyRoomSearchResultScreen(vm: StudyRoomSearchResultViewModel(studyRoomService: StudyRoomsService()),query: $query, size: .big) + } + }.searchStyle() + } + + case .calendar: + ScrollView { + Group { + if preview { + EventSearchResultScreen(vm: EventSearchResultViewModel(model: Model_Preview(), lecturesService: LecturesService_Preview(), calendarService: CalendarService_Preview()), query: $query, size: .big) + } else { + EventSearchResultScreen(vm: EventSearchResultViewModel(model: self.vm.model, lecturesService: LecturesService(), calendarService: CalendarService()), query: $query, size: .big) + } + }.searchStyle() + } + + case .movie: + ScrollView { + Group { + if preview { + MovieSearchResultScreen(vm: MovieSearchResultViewModel(service: MovieService_Preview()), query: $query, size: .big) + } else { + MovieSearchResultScreen(vm: MovieSearchResultViewModel(service: MovieService()), query: $query, size: .big) + } + }.searchStyle() + } + case .roomFinder: + ScrollView { + RoomFinderSearchResultScreen(vm: RoomFinderSearchResultViewModel(), query: $query) + .searchStyle() + } + case .lectureSearch: + ScrollView { + LectureSearchResultScreen(vm: LectureSearchResultViewModel(model: vm.model), query: $query, size: .big) + .searchStyle() + } + case .personSearch: + ScrollView { + PersonSearchResultScreen(vm: PersonSearchResultViewModel(model: vm.model), query: $query, size: .big) + .searchStyle() + } + case .all: + ScrollView { + ForEach(vm.orderedTypes, id: \.rawValue) { type in + switch type { + case .Grade: + if preview { + GradesSearchResultScreen(vm: GradesSearchResultViewModel(model: vm.model, service: GradesService_Preview()), query: $query) + } else { + GradesSearchResultScreen(vm: GradesSearchResultViewModel(model: vm.model, service: GradesService()), query: $query) + } + case .Cafeteria: + if preview { + CafeteriaSearchResultScreen(vm: CafeteriaSearchResultViewModel(service: CafeteriasService_Preview()), query: $query) + } else { + CafeteriaSearchResultScreen(vm: CafeteriaSearchResultViewModel(service: CafeteriasService()), query: $query) + } + + case .News: + if preview { + NewsSearchResultScreen(vm: NewsSearchResultViewModel(service: NewsService_Preview()), query: $query) + } else { + NewsSearchResultScreen(vm: NewsSearchResultViewModel(service: NewsService()), query: $query) + } + + case .StudyRoom: + if preview { + StudyRoomSearchResultScreen(vm: StudyRoomSearchResultViewModel(studyRoomService: StudyRoomsService_Preview()), query: $query) + } else { + StudyRoomSearchResultScreen(vm: StudyRoomSearchResultViewModel(studyRoomService: StudyRoomsService()),query: $query) + } + + case .Calendar: + if preview { + EventSearchResultScreen(vm: EventSearchResultViewModel(model: Model_Preview(), lecturesService: LecturesService_Preview(), calendarService: CalendarService_Preview()), query: $query) + } else { + EventSearchResultScreen(vm: EventSearchResultViewModel(model: self.vm.model, lecturesService: LecturesService(), calendarService: CalendarService()), query: $query) + } + + case .Movie: + if preview { + MovieSearchResultScreen(vm: MovieSearchResultViewModel(service: MovieService_Preview()), query: $query, size: .big) + } else { + MovieSearchResultScreen(vm: MovieSearchResultViewModel(service: MovieService()), query: $query, size: .big) + } + } + } + .cornerRadius(25) + .padding() + .shadow(color: .gray.opacity(0.8), radius: 5) + Group { + RoomFinderSearchResultScreen(vm: RoomFinderSearchResultViewModel(), query: $query) + LectureSearchResultScreen(vm: LectureSearchResultViewModel(model: vm.model), query: $query) + PersonSearchResultScreen(vm: PersonSearchResultViewModel(model: vm.model), query: $query) + } + .cornerRadius(25) + .padding() + .shadow(color: .gray.opacity(0.8), radius: 10) + } + } + } + } + }.onChange(of: query) { newQuery in + vm.search(for: newQuery) + } + .onAppear { + vm.search(for: query) + } + } +} + +struct Search: ViewModifier { + func body(content: Content) -> some View { + content.cornerRadius(25) + .padding() + .shadow(color: .gray.opacity(0.8), radius: 5) + } +} + +struct SearchResultView_Previews: PreviewProvider { + static var previews: some View { + SearchResultView(vm: SearchResultViewModel(model: Model_Preview()), query: .constant("StudyRoom Garching")) + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/Cafeteria/CafeteriaSearchResultScreen.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/Cafeteria/CafeteriaSearchResultScreen.swift new file mode 100644 index 00000000..d9df7f71 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/Cafeteria/CafeteriaSearchResultScreen.swift @@ -0,0 +1,49 @@ +// +// CafeteriaSearchResultScreen.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import SwiftUI + +@MainActor +struct CafeteriaSearchResultScreen: View { + @StateObject var vm: CafeteriaSearchResultViewModel + @Binding var query: String + @State var size: ResultSize = .small + + var body: some View { + Group { + switch vm.state { + case .success(let data): + CafeteriaSearchResultView(allResults: data, size: self.size) + case .loading, .na: + SearchResultLoadingView(title: "Cafeterias") + case .failed(let error): + SearchResultErrorView(title: "Cafeterias", error: error.localizedDescription) + } + }.onChange(of: query) { newQuery in + Task { + await vm.cafeteriasSearch(for: newQuery) + } + }.task { + await vm.cafeteriasSearch(for: query) + } + } +} + +struct CafeteriasSearchResultScreen_Previews: PreviewProvider { + static var previews: some View { + CafeteriaSearchResultScreen(vm: CafeteriaSearchResultViewModel(service: CafeteriasService_Preview()), query: .constant("Garching")) + .cornerRadius(25) + .padding() + .shadow(color: .gray.opacity(0.8), radius: 10) + } +} + +struct CafeteriasService_Preview: CafeteriasServiceProtocol { + func fetch(forcedRefresh: Bool) async throws -> [Cafeteria] { + return Cafeteria.previewData + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/Cafeteria/CafeteriaSearchResultView.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/Cafeteria/CafeteriaSearchResultView.swift new file mode 100644 index 00000000..2459f59b --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/Cafeteria/CafeteriaSearchResultView.swift @@ -0,0 +1,65 @@ +// +// CafeteriaSearchResultView.swift +// Campus-iOS +// +// Created by David Lin on 27.12.22. +// + +import SwiftUI + +struct CafeteriaSearchResultView: View { + + let allResults: [(cafeteria: Cafeteria, distances: Distances)] + @State var size: ResultSize = .small + @State var cafeteriaMealPlan: Cafeteria? = nil + + var results: [(cafeteria: Cafeteria, distances: Distances)] { + switch size { + case .small: + return Array(allResults.prefix(3)) + case .big: + return Array(allResults.prefix(10)) + } + } + + var body: some View { + ZStack { + Color.white + VStack { + VStack { + ZStack { + Text("Cafeterias") + .fontWeight(.bold) + .font(.title) + ExpandIcon(size: $size) + } + } + Spacer() + ScrollView { + ForEach(self.results, id: \.0) { result in + CafeteriaRowView(cafeteria: .constant(result.cafeteria)) + .padding([.leading, .trailing]) + .onTapGesture { + withAnimation(.easeIn(duration: 0.1)) { + cafeteriaMealPlan = cafeteriaMealPlan == result.cafeteria ? nil : result.cafeteria + } + } + } + } + + } + }.sheet(item: $cafeteriaMealPlan, content: { cafeteria in + NavigationView { + VStack(alignment: .leading) { + MealPlanScreen(cafeteria: cafeteria) + } + .navigationBarTitle(Text(cafeteria.title ?? "Current Cafeteria"), displayMode: .inline) + .navigationBarItems(trailing: Button(action: { + cafeteriaMealPlan = nil + }) { + Text("Done").bold() + }) + } + }) + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/Event/EventSearchResultScreen.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/Event/EventSearchResultScreen.swift new file mode 100644 index 00000000..e5c489d6 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/Event/EventSearchResultScreen.swift @@ -0,0 +1,54 @@ +// +// EventSearchResultScreen.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import SwiftUI + +struct EventSearchResultScreen: View { + @StateObject var vm: EventSearchResultViewModel + @Binding var query: String + @State var size: ResultSize = .small + + var body: some View { + Group { + switch vm.state { + case .success(let data): + EventSearchResultView(allResults: data, model: vm.model, size: self.size) + case .loading, .na: + SearchResultLoadingView(title: "Lectures") + case .failed(let error): + SearchResultErrorView(title: "Lectures", error: error.localizedDescription) + } + }.onChange(of: query) { newQuery in + Task { + await vm.eventsSearch(for: query) + } + }.task { + await vm.eventsSearch(for: query) + } + } +} + +struct EventSearchResultScreen_Previews: PreviewProvider { + static var previews: some View { + EventSearchResultScreen(vm: EventSearchResultViewModel(model: Model_Preview(), lecturesService: LecturesService_Preview(), calendarService: CalendarService_Preview()), query: .constant("Analysis")) + .cornerRadius(25) + .padding() + .shadow(color: .gray.opacity(0.8), radius: 10) + } +} + +struct LecturesService_Preview: LecturesServiceProtocol { + func fetch(token: String, forcedRefresh: Bool = false) async throws -> [Lecture] { + return Lecture.dummyData + } +} + +struct CalendarService_Preview: CalendarServiceProtocol { + func fetch(token: String, forcedRefresh: Bool) async throws -> [CalendarEvent] { + return CalendarEvent.previewData + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/Event/EventSearchResultView.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/Event/EventSearchResultView.swift new file mode 100644 index 00000000..77e53eb9 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/Event/EventSearchResultView.swift @@ -0,0 +1,154 @@ +// +// CalendarLectureSearchResultView.swift +// Campus-iOS +// +// Created by David Lin on 28.12.22. +// + +import SwiftUI + +struct EventSearchResultView: View { + let allResults: [(event: EventSearchResult, distance: Distances)] + let model: Model + @State var showEventsFor: EventSearchResult? = nil + @State var showDetailedLecture: Lecture? = nil + @State var size: ResultSize = .small + + var results: [(event: EventSearchResult, distance: Distances)] { + switch size { + case .small: + return Array(allResults.prefix(3)) + case .big: + return Array(allResults.prefix(10)) + } + } + + var body: some View { + ZStack { + Color.white + VStack{ + VStack { + ZStack { + Text("Lectures") + .fontWeight(.bold) + .font(.title) + ExpandIcon(size: $size) + } + } + ScrollView { + ForEach(self.results, id: \.event) { result in + VStack(alignment: .leading) { + HStack { + Text(result.event.lecture.title) + + Spacer() + NavigationLink { + LectureDetailsScreen(model: self.model, lecture: result.event.lecture) + .navigationBarTitleDisplayMode(.inline) + } label: { + Image(systemName: "info.circle") + .foregroundColor(.tumBlue) + } + + } + if result.event.events.count > 0 { + Button { + withAnimation { + self.showEventsFor = showEventsFor == result.event ? nil : result.event + } + } label: { + if self.showEventsFor == result.event { + Text("Hide next events") + .fontWeight(.light) + .foregroundColor(.gray) + } else { + Text("Show next events") + .fontWeight(.light) + .foregroundColor(.gray) + } + + } + if let shownResultEvents = showEventsFor, showEventsFor == result.event { + ScrollView { + VStack { + ForEach(shownResultEvents.events, id: \.id) { event in + VStack(alignment: .leading, spacing: 8) { + LectureDetailsBasicInfoRowView( + iconName: "hourglass", + text: duration(event) + ) + Divider() + // Open RoomfinderView + VStack (alignment: .leading) { + LectureDetailsBasicInfoRowView( + iconName: "rectangle.portrait.arrowtriangle.2.inward", + text: location(event) + ) + HStack { + Spacer() + NavigationLink(destination: RoomFinderView(model: self.model, viewModel: RoomFinderViewModel(), searchText: extract(room: location(event)))) { + HStack { + Text("Open in RoomFinder") + Image(systemName: "arrow.right.circle") + }.foregroundColor(Color(UIColor.tumBlue)) + .font(.footnote) + } + } + } + Divider() + Divider() + } + } + } + } + } + } + }.padding() + } + } + } + } + } + + func duration(_ event: CalendarEvent) -> String { + if let start = event.startDate, let end = event.endDate { + return "\(Self.startDateFormatter.string(from: start)) - \(Self.endDateFormatter.string(from: end))" + } + return "n/a" + } + + func location(_ event: CalendarEvent) -> String { + if let location = event.location { + return location + } + return "n/a" + } + + static let startDateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale.current + formatter.dateFormat = "EE, dd.MM.yyyy, HH:mm" + return formatter + }() + + static let endDateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale.current + formatter.dateFormat = "HH:mm" + return formatter + }() + + func extract(room: String) -> String { + var roomNumber = room + + if let openBraceRange = roomNumber.range(of: "(") { + roomNumber.removeSubrange(roomNumber.startIndex.. [Grade] { + return Grade.previewData + } + + func fetchGrades(token: String, forcedRefresh: Bool) async throws -> [Grade] { + return Grade.previewData + } + + func fetchGradesSemesterDegrees(token: String, forcedRefresh: Bool) async throws -> GradesSemesterDegrees { + return [] + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/Lecture/LectureSearchResultScreen.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/Lecture/LectureSearchResultScreen.swift new file mode 100644 index 00000000..b0845f16 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/Lecture/LectureSearchResultScreen.swift @@ -0,0 +1,33 @@ +// +// LectureSearchResultScreen.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import SwiftUI + +struct LectureSearchResultScreen: View { + @StateObject var vm: LectureSearchResultViewModel + @Binding var query: String + @State var size: ResultSize = .small + + var body: some View { + Group { + switch vm.state { + case .success(let data): + LectureSearchResultView(allResults: data, model: vm.model, size: self.size) + case .loading, .na: + SearchResultLoadingView(title: "Lectures") + case .failed(let error): + SearchResultErrorView(title: "Lectures", error: error.localizedDescription) + } + }.onChange(of: query) { newQuery in + Task { + await vm.lectureSearch(for: newQuery) + } + }.task { + await vm.lectureSearch(for: query) + } + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/Lecture/LectureSearchResultView.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/Lecture/LectureSearchResultView.swift new file mode 100644 index 00000000..0411b6e5 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/Lecture/LectureSearchResultView.swift @@ -0,0 +1,63 @@ +// +// LectureSearchResultView.swift +// Campus-iOS +// +// Created by David Lin on 14.01.23. +// + +import Foundation +import SwiftUI + +struct LectureSearchResultView: View { + + let allResults: [Lecture] + let model: Model + @State var size: ResultSize = .small + + var results: [Lecture] { + switch size { + case .small: + return Array(allResults.prefix(3)) + case .big: + return Array(allResults.prefix(10)) + } + } + + var body: some View { + ZStack { + Color.white + VStack { + VStack { + ZStack { + Text("Lecture Search") + .fontWeight(.bold) + .font(.title) + ExpandIcon(size: $size) + } + } + ScrollView { + ForEach(self.results, id: \.id) { result in + VStack(alignment: .leading) { + NavigationLink { + LectureDetailsScreen(model: self.model, lecture: result) + .navigationBarTitleDisplayMode(.inline) + } label: { + HStack { + Text(result.title) + Spacer() + Image(systemName: "info.circle") + .foregroundColor(.tumBlue) + } + }.buttonStyle(.plain) + Divider() + } + } + } + if self.results.count == 0 { + Text("No lectures were found 😢") + .foregroundColor(.gray) + } + }.padding() + } + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/Movie/MovieSearchResultScreen.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/Movie/MovieSearchResultScreen.swift new file mode 100644 index 00000000..06173ea5 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/Movie/MovieSearchResultScreen.swift @@ -0,0 +1,48 @@ +// +// SwiftUIView.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import SwiftUI + +struct MovieSearchResultScreen: View { + @StateObject var vm: MovieSearchResultViewModel + @Binding var query: String + @State var size: ResultSize = .small + + var body: some View { + Group { + switch vm.state { + case .success(let data): + MovieSearchResultView(allResults: data, size: self.size) + case .loading, .na: + SearchResultLoadingView(title: "Movies") + case .failed(let error): + SearchResultErrorView(title: "Movies", error: error.localizedDescription) + } + }.onChange(of: query) { newQuery in + Task { + await vm.movieSearch(for: newQuery) + } + }.task { + await vm.movieSearch(for: query) + } + } +} + +struct MovieSearchResultScreen_Previews: PreviewProvider { + static var previews: some View { + MovieSearchResultScreen(vm: MovieSearchResultViewModel(service: MovieService_Preview()), query: .constant("news test")) + .cornerRadius(25) + .padding() + .shadow(color: .gray.opacity(0.8), radius: 10) + } +} + +struct MovieService_Preview: MovieServiceProtocol { + func fetch(forcedRefresh: Bool) async throws -> [Movie] { + return Movie.previewData + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/Movie/MovieSearchResultView.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/Movie/MovieSearchResultView.swift new file mode 100644 index 00000000..34f93c1a --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/Movie/MovieSearchResultView.swift @@ -0,0 +1,68 @@ +// +// MovieSearchResultView.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import SwiftUI + +struct MovieSearchResultView: View { + let allResults: [(movie: Movie, distance: Distances)] + + @State var newsLink: URL? = nil + @State var selectedMovie: Movie? = nil + + @State var size: ResultSize = .small + + var results: [(movie: Movie, distance: Distances)] { + switch size { + case .small: + return Array(allResults.prefix(3)) + case .big: + return Array(allResults.prefix(10)) + } + } + + var body: some View { + ZStack { + Color.white + VStack { + VStack { + ZStack { + Text("Movies").fontWeight(.bold) + .font(.title) + ExpandIcon(size: $size) + } + } + if results.isEmpty { + Text("No more movies this semester 😢\nGet excited for the next season!") + .padding() + } else { + ScrollView { + ForEach(results, id: \.movie) { result in + HStack { + Button { + self.selectedMovie = result.movie + } label: { + VStack(alignment: .leading) { + Text(result.movie.title ?? "") + .fontWeight(.bold) + Text(result.movie.genre ?? "") + .fontWeight(.light) + .foregroundColor(.gray) + } + .multilineTextAlignment(.leading) + .padding() + } + Spacer() + } + }.sheet(item: $selectedMovie) { selectedMovie in + MovieDetailedView(movie: selectedMovie) + } + } + } + } + } + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/News/NewsSearchResultScreen.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/News/NewsSearchResultScreen.swift new file mode 100644 index 00000000..860f3a92 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/News/NewsSearchResultScreen.swift @@ -0,0 +1,52 @@ +// +// NewsSearchResultScreen.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import SwiftUI + +struct NewsSearchResultScreen: View { + @StateObject var vm: NewsSearchResultViewModel + @Binding var query: String + @State var size: ResultSize = .small + + var body: some View { + Group { + switch vm.state { + case .success(let data): + NewsSearchResultView(allResults: data, size: self.size) + case .loading, .na: + SearchResultLoadingView(title: "Movies") + case .failed(let error): + SearchResultErrorView(title: "Movies", error: error.localizedDescription) + } + }.onChange(of: query) { newQuery in + Task { + await vm.newsSearch(for: newQuery) + } + }.task { + await vm.newsSearch(for: query) + } + } +} + +struct NewsSearchResultScreen_Previews: PreviewProvider { + static var previews: some View { + NewsSearchResultScreen(vm: NewsSearchResultViewModel(service: NewsService_Preview()), query: .constant("news test")) + .cornerRadius(25) + .padding() + .shadow(color: .gray.opacity(0.8), radius: 10) + } +} + +struct NewsService_Preview: NewsServiceProtocol { + func fetch(forcedRefresh: Bool) async throws -> [NewsSource] { + return NewsSource.previewData + } + + func fetch(forcedRefresh: Bool, source: String) async throws -> [News] { + return News.previewData + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/News/NewsSearchResultView.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/News/NewsSearchResultView.swift new file mode 100644 index 00000000..b6a2d4d8 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/News/NewsSearchResultView.swift @@ -0,0 +1,64 @@ +// +// NewsSearchResultView.swift +// Campus-iOS +// +// Created by David Lin on 13.01.23. +// + +import SwiftUI + +struct NewsSearchResultView: View { + let allResults: [(news: News, distance: Distances)] + @State var newsLink: URL? = nil + @State var size: ResultSize = .small + + var results: [(news: News, distance: Distances)] { + switch size { + case .small: + return Array(allResults.prefix(3)) + case .big: + return Array(allResults.prefix(10)) + } + } + + var body: some View { + ZStack { + Color.white + VStack { + VStack { + ZStack { + Text("News").fontWeight(.bold) + .font(.title) + ExpandIcon(size: $size) + } + } + ScrollView { + ///** The following code is for all newsSources. Currently we only use TUMOnline due to lagginess ** + // ForEach(vm.results, id: \.newsResult) { result in + // VStack { + // Text(result.newsResult.title ?? "") + // ForEach(result.newsResult.news, id: \.id) { news in + // Text(news.title ?? "") + // } + // } + // } + ForEach(results, id: \.news) { result in + HStack { + Button { + self.newsLink = result.news.link + } label: { + Text(result.news.title ?? "") + .fontWeight(.bold) + .multilineTextAlignment(.leading) + .padding() + } + Spacer() + } + }.sheet(item: $newsLink) { selectedLink in + SFSafariViewWrapper(url: selectedLink) + } + } + } + } + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/Person/PersonSearchResultScreen.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/Person/PersonSearchResultScreen.swift new file mode 100644 index 00000000..10d4a560 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/Person/PersonSearchResultScreen.swift @@ -0,0 +1,35 @@ +// +// PersonSearchResultScreen.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import SwiftUI + +@MainActor +struct PersonSearchResultScreen: View { + @StateObject var vm: PersonSearchResultViewModel + @Binding var query: String + @State var size: ResultSize = .small + + var body: some View { + Group { + switch vm.state { + case .success(let data): + PersonSearchResultView(allResults: data, model: vm.model, size: self.size) + case .loading, .na: + SearchResultLoadingView(title: "Person Search") + case .failed(let error): + SearchResultErrorView(title: "Person Search", error: error.localizedDescription) + } + }.onChange(of: query) { newQuery in + Task { + await vm.personSearch(for: newQuery) + } + } + .task { + await vm.personSearch(for: query) + } + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/Person/PersonSearchResultView.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/Person/PersonSearchResultView.swift new file mode 100644 index 00000000..120e97ac --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/Person/PersonSearchResultView.swift @@ -0,0 +1,57 @@ +// +// PersonSearchResultView.swift +// Campus-iOS +// +// Created by David Lin on 14.01.23. +// + +import SwiftUI + +struct PersonSearchResultView: View { + let allResults: [Person] + let model: Model + @State var size: ResultSize = .small + + var results: [Person] { + switch size { + case .small: + return Array(allResults.prefix(3)) + case .big: + return allResults + } + } + + var body: some View { + ZStack { + Color.white + VStack { + VStack { + ZStack { + Text("Person Search") + .fontWeight(.bold) + .font(.title) + ExpandIcon(size: $size) + } + } + ScrollView { + ForEach(results, id: \.id) { result in + VStack(alignment: .leading) { + NavigationLink( + destination: PersonDetailedScreen(model: model, person: result) + .navigationBarTitleDisplayMode(.inline) + ) { + HStack { + Text(result.fullName) + Spacer() + Image(systemName: "info.circle") + .foregroundColor(.tumBlue) + } + }.buttonStyle(.plain) + Divider() + } + } + } + }.padding() + } + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/RoomFinder/RoomFinderSearchResultScreen.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/RoomFinder/RoomFinderSearchResultScreen.swift new file mode 100644 index 00000000..fff85304 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/RoomFinder/RoomFinderSearchResultScreen.swift @@ -0,0 +1,34 @@ +// +// RoomFinderSearchResultScreen.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import SwiftUI + +@MainActor +struct RoomFinderSearchResultScreen: View { + @StateObject var vm: RoomFinderSearchResultViewModel + @Binding var query: String + @State var size: ResultSize = .small + + var body: some View { + Group { + switch vm.state { + case .success(let data): + RoomFinderSearchResultView(allResults: data, size: self.size) + case .loading, .na: + SearchResultLoadingView(title: "RoomFinder") + case .failed(let error): + SearchResultErrorView(title: "RoomFinder", error: error.localizedDescription) + } + }.onChange(of: query) { newQuery in + Task { + await vm.roomFinderSearch(for: newQuery) + } + }.task { + await vm.roomFinderSearch(for: query) + } + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/RoomFinder/RoomFinderSearchResultView.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/RoomFinder/RoomFinderSearchResultView.swift new file mode 100644 index 00000000..e1119655 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/RoomFinder/RoomFinderSearchResultView.swift @@ -0,0 +1,53 @@ +// +// RoomFinderSearchView.swift +// Campus-iOS +// +// Created by David Lin on 13.01.23. +// + +import SwiftUI + +struct RoomFinderSearchResultView: View { + let allResults: [NavigaTumNavigationEntity] + @State var size: ResultSize = .small + + var results: [NavigaTumNavigationEntity] { + switch size { + case .small: + return Array(allResults.prefix(3)) + case .big: + return Array(allResults.prefix(10)) + } + } + + var body: some View { + ZStack { + Color.white + VStack { + VStack { + ZStack { + Text("NavigaTUM") + .fontWeight(.bold) + .font(.title) + ExpandIcon(size: $size) + } + } + ScrollView { + VStack(alignment: .leading) { + ForEach(results, id:\.id) { room in + NavigationLink( + destination: NavigaTumDetailsView(viewModel: NavigaTumDetailsViewModel(id: room.id)) + ) { + Text(room.name).padding() + } + } + } + } + if self.results.count == 0 { + Text("No rooms were found 😢") + .foregroundColor(.gray) + } + }.padding() + } + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/StudyRoom/StudyRoomSearchResultScreen.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/StudyRoom/StudyRoomSearchResultScreen.swift new file mode 100644 index 00000000..fcf4a05c --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/StudyRoom/StudyRoomSearchResultScreen.swift @@ -0,0 +1,46 @@ +// +// SwiftUIView.swift +// Campus-iOS +// +// Created by David Lin on 07.03.23. +// + +import SwiftUI + +struct StudyRoomSearchResultScreen: View { + @StateObject var vm: StudyRoomSearchResultViewModel + @Binding var query: String + @State var size: ResultSize = .small + + var body: some View { + Group { + switch vm.state { + case .success(let data): + StudyRoomSearchResultView(allResults: data, size: self.size) + case .loading, .na: + SearchResultLoadingView(title: "StudyRooms") + case .failed(let error): + SearchResultErrorView(title: "StudyRooms", error: error.localizedDescription) + } + }.onChange(of: query) { newQuery in + Task { + + await vm.studyRoomSearch(for: newQuery) + } + }.task { + await vm.studyRoomSearch(for: query) + } + } +} + +struct StudyRoomSearchResultScreen_Previews: PreviewProvider { + static var previews: some View { + StudyRoomSearchResultScreen(vm: StudyRoomSearchResultViewModel(studyRoomService: StudyRoomsService_Preview()), query: .constant("studyroom garching")) + } +} + +struct StudyRoomsService_Preview: StudyRoomsServiceProtocol { + func fetch(forcedRefresh: Bool) async throws -> StudyRoomApiRespose { + return StudyRoomApiRespose.previewData + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchResultViews/StudyRoom/StudyRoomSearchResultView.swift b/Campus-iOS/SearchComponent/Views/SearchResultViews/StudyRoom/StudyRoomSearchResultView.swift new file mode 100644 index 00000000..e0361555 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchResultViews/StudyRoom/StudyRoomSearchResultView.swift @@ -0,0 +1,76 @@ +// +// StudyRoomSearchResultView.swift +// Campus-iOS +// +// Created by David Lin on 27.12.22. +// + +import SwiftUI + +struct StudyRoomSearchResultView: View { + var allResults: [(studyRoomResult: StudyRoomSearchResult, distance: Distances)] + @State var showRoomsForGroup: [StudyRoom]? = nil + @State var size: ResultSize = .small + + var results: [(studyRoomResult: StudyRoomSearchResult, distance: Distances)] { + switch size { + case .small: + return Array(allResults.prefix(3)) + case .big: + return Array(allResults.prefix(10)) + } + } + + var body: some View { + ZStack { + Color.white + VStack(alignment: .leading) { + VStack { + ZStack { + Text("Study Rooms") + .fontWeight(.bold) + .font(.title) + ExpandIcon(size: $size) + } + } + ScrollView { + ForEach(results, id: \.studyRoomResult) { result in + VStack (alignment: .leading) { + HStack { + Text(result.studyRoomResult.group.name ?? "no group name") + .fontWeight(.heavy) + Spacer() + GroupDirectionsButton(group: result.studyRoomResult.group) { + Image(systemName: "mappin.and.ellipse") + } + } + Button { + withAnimation { + self.showRoomsForGroup = showRoomsForGroup == result.studyRoomResult.rooms ? nil : result.studyRoomResult.rooms + } + } label: { + if showRoomsForGroup == result.studyRoomResult.rooms { + Text("Hide rooms") + .foregroundColor(.gray) + .fontWeight(.light) + } else { + Text("Show rooms") + .foregroundColor(.gray) + .fontWeight(.light) + } + } + + + if let rooms = showRoomsForGroup, showRoomsForGroup == result.studyRoomResult.rooms { + ForEach(rooms) { room in + StudyRoomCell(room: room) + .tint(.black) + } + } + } + } + }.padding() + } + } + } +} diff --git a/Campus-iOS/SearchComponent/Views/SearchView.swift b/Campus-iOS/SearchComponent/Views/SearchView.swift new file mode 100644 index 00000000..9cf8e082 --- /dev/null +++ b/Campus-iOS/SearchComponent/Views/SearchView.swift @@ -0,0 +1,24 @@ +// +// SearchView.swift +// Campus-iOS +// +// Created by David Lin on 27.12.22. +// + +import SwiftUI + +struct SearchView : View { + + @ObservedObject var model: Model + @Binding var query: String + @Environment(\.isSearching) var isSearching + @ViewBuilder let content: () -> Content + + var body: some View { + if isSearching { + SearchResultView(vm: SearchResultViewModel(model: self.model), query: $query) + } else { + content() + } + } +} diff --git a/Campus-iOS/TUMSexyComponent/ViewModel/TUMSexyViewModel.swift b/Campus-iOS/TUMSexyComponent/ViewModel/TUMSexyViewModel.swift index f1abc82d..3b9e0a3d 100644 --- a/Campus-iOS/TUMSexyComponent/ViewModel/TUMSexyViewModel.swift +++ b/Campus-iOS/TUMSexyComponent/ViewModel/TUMSexyViewModel.swift @@ -30,33 +30,3 @@ class TUMSexyViewModel: ObservableObject { } } } - -//class TUMSexyViewModel: ObservableObject { -// @Published var links: [TUMSexyLink] = [] -// -// typealias ImporterType = Importer -// private let importer = ImporterType(endpoint: TUMSexyAPI()) -// -// -// init() { -// // TODO: Get from cache, if not found, then fetch -// fetch() -// } -// -// func fetch() { -// importer.performFetch( handler: { result in -// switch result { -// case .success(let storage): -// var filledLinks = [TUMSexyLink]() -// storage.values.forEach() { -// if $0.target != nil && $0.description != nil { -// filledLinks.append($0) -// } -// } -// self.links = filledLinks -// case .failure(let error): -// print(error) -// } -// }) -// } -//} diff --git a/Campus-iOS/TUMSexyComponent/Views/TUMSexyView.swift b/Campus-iOS/TUMSexyComponent/Views/TUMSexyView.swift index 3899a3b7..c6282baf 100644 --- a/Campus-iOS/TUMSexyComponent/Views/TUMSexyView.swift +++ b/Campus-iOS/TUMSexyComponent/Views/TUMSexyView.swift @@ -31,8 +31,6 @@ struct TUMSexyView: View { Link(link.description ?? "", destination: URL(string: link.target ?? "")!) } } - -// } } .searchable(text: $searchText) diff --git a/Campus-iOS/ProfileComponent/Entity/Tuition.swift b/Campus-iOS/TuitionComponent/Model/Tuition.swift similarity index 100% rename from Campus-iOS/ProfileComponent/Entity/Tuition.swift rename to Campus-iOS/TuitionComponent/Model/Tuition.swift diff --git a/Campus-iOS/TuitionComponent/View/TuitionScreen.swift b/Campus-iOS/TuitionComponent/View/TuitionScreen.swift new file mode 100644 index 00000000..c1a215bc --- /dev/null +++ b/Campus-iOS/TuitionComponent/View/TuitionScreen.swift @@ -0,0 +1,47 @@ +// +// ProfileTuitionNavigationLink.swift +// Campus-iOS +// +// Created by Milen Vitanov on 13.02.22. +// + +import SwiftUI + +struct TuitionScreen: View { + + @StateObject var vm: ProfileViewModel + + var body: some View { + Group { + switch vm.tuitionState { + case .success(let tuition): + NavigationLink(destination: TuitionView(tuition: tuition).navigationBarTitle(Text("Tuition fees"))) { + Label { + HStack { + Text("Tuition fees") + if !tuition.isOpenAmount { + Spacer() + Text("✅") + } else { + if let amount = tuition.amount, let formattedAmount = OpenTuitionAmountView.currencyFormatter.string(from: amount) { + Spacer() + Text(formattedAmount).foregroundColor(.red) + } else { + Text("Open amount couldn't be fetched.") + } + } + } + } icon: { + Image(systemName: "eurosign.circle") + } + } + case .loading, .na: + Text("Loading") + case .failed(error: let error): + Text(error.localizedDescription) + } + }.task { + await vm.getTuition(forcedRefresh: true) + } + } +} diff --git a/Campus-iOS/WidgetComponent/Screen/WidgetScreen.swift b/Campus-iOS/WidgetComponent/Screen/WidgetScreen.swift index ae3a86f4..7b22f812 100644 --- a/Campus-iOS/WidgetComponent/Screen/WidgetScreen.swift +++ b/Campus-iOS/WidgetComponent/Screen/WidgetScreen.swift @@ -10,11 +10,15 @@ import MapKit struct WidgetScreen: View { + @Environment(\.isSearching) var isSearching + @StateObject private var recommender: WidgetRecommender var model: Model var profileViewModel: ProfileViewModel @State private var refresh = false - @State private var widgetTitle = "" + @State private var widgetTitle = String() + @State private var searchString = "" + private let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect() init(model: Model) { @@ -30,16 +34,19 @@ struct WidgetScreen: View { case .loading: ProgressView() case .success: - ScrollView { - self.generateContent( - views: recommender.recommendations.map { recommender.getWidget(for: $0.widget, size: $0.size(), refresh: $refresh) }, widgetTitle: self.widgetTitle - ) + SearchView(model: self.model, query: $searchString) { + ScrollView { + self.generateContent( + views: recommender.recommendations.map { recommender.getWidget(for: $0.widget, size: $0.size(), refresh: $refresh) }, widgetTitle: self.widgetTitle + ) .frame(maxWidth: .infinity) + } + .refreshable { + try? await recommender.fetchRecommendations() + refresh.toggle() + } } - .refreshable { - try? await recommender.fetchRecommendations() - refresh.toggle() - } + .searchable(text: $searchString, placement: .navigationBarDrawer(displayMode: .always)) } } .task { @@ -53,7 +60,7 @@ struct WidgetScreen: View { } } .onReceive(timer) { _ in - refresh.toggle() + refresh.toggle() } } @@ -108,5 +115,12 @@ struct WidgetScreen: View { } } .navigationTitle(widgetTitle) + .navigationBarTitleDisplayMode(.large) + } +} + +struct WidgetScreen_Previews: PreviewProvider { + static var previews: some View { + WidgetScreen(model: Model()) } } diff --git a/Campus-iOSTests/Campus_iOSTests.swift b/Campus-iOSTests/Campus_iOSTests.swift index c915b385..cc862de3 100644 --- a/Campus-iOSTests/Campus_iOSTests.swift +++ b/Campus-iOSTests/Campus_iOSTests.swift @@ -17,17 +17,349 @@ class Campus_iOSTests: XCTestCase { override func tearDownWithError() throws { // Put teardown code here. This method is called after the invocation of each test method in the class. } + + func testtest() throws { + let a = "hello" + let b = "hell0" + + let x = LevenshteinGreenlee.levenshtein(stringA: a, stringB: b) + } - func testExample() throws { + func testIfEqualResults() throws { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. + + let classic = apply(method: LevenshteinGreenlee.levenshtein) + let wiki = apply(method: LevenshteinWiki.distanceBetween) + let marino = apply(method: LevenshteinNickyMarino.opti_leven_distance) + + print(classic) + print(wiki) + print(marino) + + if classic.count == marino.count { + for i in classic.indices { + if classic[i] != marino[i] { + print("At \(i): \(classic[i])") + print("At \(i): \(marino[i])") + assert(false) + } + } + } + + if wiki.count == classic.count { + for i in classic.indices { + if classic[i] != wiki[i] { + print("At \(i): \(classic[i])") + print("At \(i): \(wiki[i])") + assert(false) + } + } + } } - func testPerformanceExample() throws { + func testPerformanceLev1() throws { // This is an example of a performance test case. + + var results = [Int]() + + self.measure { + results = apply(method: LevenshteinGreenlee.levenshtein) + } + + print(">>>>>>RESULTS Lev1<<<<<<<") + print(results) + } + + func testPerformanceLev2() throws { + var results = [Int]() + + self.measure { + results = apply(method: LevenshteinWiki.distanceBetween) + } + + print(">>>>>>RESULTS Lev2<<<<<<<") + print(results) + } + + func testPerformanceLev3() throws { + var results = [Int]() + self.measure { - // Put the code you want to measure the time of here. + results = apply(method: LevenshteinNickyMarino.opti_leven_distance) + } + + print(">>>>>>RESULTS Lev3<<<<<<<") + print(results) + } + + +} + +let A = randomStrings(amount: 30) +let B = randomStrings(amount: 30) + +func apply(method: (String, String) -> Int) -> [Int] { + + var results = [Int]() + + print("applying started") + print(A.count) + print(B.count) + if A.count == B.count { + A.forEach { stringA in + B.forEach { stringB in + print("StringA: \(stringA) and StringB: \(stringB)") + results.append(method(stringA, stringB)) + + } } } + print("applying finished") + + return results +} + +func randomStrings(amount: Int) -> [String] { + var strings = [String]() + for _ in (1.. String { + let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 " + return String((0.. Int { + return numbers.reduce(numbers[0], {$0 < $1 ? $0 : $1}) + } + + class Array2D { + var cols:Int, rows:Int + var matrix: [Int] + + init(cols:Int, rows:Int) { + self.cols = cols + self.rows = rows + matrix = Array(repeating: 0, count: cols*rows) + } + + subscript(col:Int, row:Int) -> Int { + get { + return matrix[cols * row + col] + } + set { + matrix[cols*row+col] = newValue + } + } + + func colCount() -> Int { + return self.cols + } + + func rowCount() -> Int { + return self.rows + } + } + + static func distanceBetween(aStr: String, and bStr: String) -> Int { + let a = Array(aStr.utf16) + let b = Array(bStr.utf16) + + let dist = Array2D(cols: a.count + 1, rows: b.count + 1) + + for i in 1...a.count { + dist[i, 0] = i + } + + for j in 1...b.count { + dist[0, j] = j + } + + for i in 1...a.count { + for j in 1...b.count { + if a[i-1] == b[j-1] { + dist[i, j] = dist[i-1, j-1] // noop + } else { + dist[i, j] = Swift.min( + dist[i-1, j] + 1, // deletion + dist[i, j-1] + 1, // insertion + dist[i-1, j-1] + 1 // substitution + ) + } + } + } + + return dist[a.count, b.count] + } +} + +struct LevenshteinGreenlee { + static func levenshtein(stringA: String, stringB: String) -> Int { + /// Either `self `or the `comparisonToken` is empty. + if stringA.isEmpty && stringB.isEmpty { + return 0 + } else if stringA.isEmpty { + return stringB.count + } else if stringB.isEmpty { + return stringA.count + } + + /// Starting with the levenshtein distance algorithm + // Create character arrays + let a = Array(stringA) + let b = Array(stringB) + + // Initialize matrix of size |a|+1 * |b|+1 to zero + var dist = [[Int]]() + for _ in 0...a.count { + dist.append([Int](repeating: 0, count: b.count + 1)) + } + + // 'a' prefixes can be transformed into empty string by deleting every char + for i in 1...a.count { + dist[i][0] = i + print(i) + } + + // 'b' prefixes can be created from empty string by inserting every char + for j in 1...b.count { + dist[0][j] = j + } + + for i in 1...a.count { + for j in 1...b.count { +// print("\(a[i-1]) = \(b[j-1])") +// print(dist) + if a[i-1] == b[j-1] { + dist[i][j] = dist[i-1][j-1] // Noop + + + } else { + dist[i][j] = Swift.min( + dist[i-1][j] + 1, // Deletion + dist[i][j-1] + 1, // Insertion + dist[i-1][j-1] + 1 // Substitution + ) + } + } + } + for i in 0...a.count { + print(dist[i]) + } + return dist[a.count][b.count] + } +} + +struct LevenshteinPackage { + private struct _LevenshteinMatrix { + let m, n: Int + + private var _values: [Int] + + init(m: Int, n: Int) { + self.m = m + self.n = n + + self._values = [Int](repeating: 0, count: m * n) + } + + subscript(i: Int, j: Int) -> Int { + get { + return _values[i + j * m] + } + set { + _values[i + j * m] = newValue + } + } + + func compute(at i: Int, _ j: Int, using u: String, _ v: String) -> Int { + let indexU = u.index(u.startIndex, offsetBy: i) + let indexV = v.index(v.startIndex, offsetBy: j) + + let a = u[indexU] == v[indexV] ? self[i - 1, j - 1] : Int.max + let b = self[i - 1, j - 1] + 1 // Replace + let c = self[i, j - 1] + 1 // Insert + let d = self[i - 1, j] + 1 // Delete + + return min(a, b, c, d) + } + } + + static func levenshteinDistance(_ u: String, _ v: String) -> Int { + let m = u.count + let n = v.count + + var D = _LevenshteinMatrix(m: m, n: n) + + D[0, 0] = 0 + + for i in 1.. Int { + // Check for empty strings first + if (a.count == 0) { + return b.count + } + if (b.count == 0) { + return a.count + } + + // Create an empty distance matrix with dimensions len(a)+1 x len(b)+1 + var dists = Array(repeating: Array(repeating: 0, count: b.count+1), count: a.count+1) + + // a's default distances are calculated by removing each character + for i in 1...(a.count) { + dists[i][0] = i + } + // b's default distances are calulated by adding each character + for j in 1...(b.count) { + dists[0][j] = j + } + + // Find the remaining distances using previous distances + for i in 1...(a.count) { + for j in 1...(b.count) { + // Calculate the substitution cost + let cost = (Array(a)[i-1] == Array(b)[j-1]) ? 0 : 1 + + dists[i][j] = min( + // Removing a character from a + dists[i-1][j] + 1, + // Adding a character to b + dists[i][j-1] + 1, + // Substituting a character from a to b + dists[i-1][j-1] + cost + ) + } + } + + return dists.last!.last! + } }