From b1405ecc51ec595dea7507665884b5e427365510 Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Sun, 19 Sep 2021 20:05:08 -0400 Subject: [PATCH 01/21] a --- PennMobile/Auth/OAuth2NetworkManager.swift | 1 + .../GSR-Booking/Model/GSRReservation.swift | 2 +- .../Cells/Calendar/HomeCalendarCell.swift | 2 +- .../Cells/Calendar/HomeCalendarCellItem.swift | 19 ++-- .../Cells/Courses/HomeCoursesCellItem.swift | 41 +++------ .../Cells/Dining/HomeDiningCellItem.swift | 42 +++------ .../Cells/Feature/HomeFeatureCellItem.swift | 3 + .../HomeGSRLocationsCellItem.swift | 4 + .../HomeReservationsCellItem.swift | 28 +++--- .../Home/Cells/GSR/HomeGSRCellItem.swift | 4 + .../HomeGroupInvitesCellItem.swift | 22 ++--- .../Cells/Laundry/HomeLaundryCellItem.swift | 4 + PennMobile/Home/Cells/News/HomeNewsCell.swift | 4 +- .../Home/Cells/News/HomeNewsCellItem.swift | 52 ++++------- PennMobile/Home/Cells/News/NewsArticle.swift | 32 +------ .../Home/Cells/Post/HomePostCellItem.swift | 3 + .../Home/Controllers/HomeViewController.swift | 37 +++----- PennMobile/Home/Model/HomeCellItem.swift | 2 +- .../Home/Networking/HomeAPIService.swift | 90 ++++--------------- 19 files changed, 131 insertions(+), 261 deletions(-) diff --git a/PennMobile/Auth/OAuth2NetworkManager.swift b/PennMobile/Auth/OAuth2NetworkManager.swift index 9cc261906..4883111a8 100644 --- a/PennMobile/Auth/OAuth2NetworkManager.swift +++ b/PennMobile/Auth/OAuth2NetworkManager.swift @@ -90,6 +90,7 @@ extension OAuth2NetworkManager { // dev token that expires in a year // use until auth is back up if let accessToken = self.currentAccessToken, Date() < accessToken.expiration { + print(accessToken.value) callback(accessToken) } else { self.currentAccessToken = nil diff --git a/PennMobile/GSR-Booking/Model/GSRReservation.swift b/PennMobile/GSR-Booking/Model/GSRReservation.swift index 277877e05..3cd4adafd 100644 --- a/PennMobile/GSR-Booking/Model/GSRReservation.swift +++ b/PennMobile/GSR-Booking/Model/GSRReservation.swift @@ -13,7 +13,7 @@ enum GSRService: String { case libcal } -struct GSRReservation: Codable { +struct GSRReservation: Codable, Equatable { let bookingId: String let gsr: GSRLocation let roomId: Int diff --git a/PennMobile/Home/Cells/Calendar/HomeCalendarCell.swift b/PennMobile/Home/Cells/Calendar/HomeCalendarCell.swift index ad279a36c..81c602df4 100644 --- a/PennMobile/Home/Cells/Calendar/HomeCalendarCell.swift +++ b/PennMobile/Home/Cells/Calendar/HomeCalendarCell.swift @@ -23,7 +23,7 @@ final class HomeCalendarCell: UITableViewCell, HomeCellConformable { static func getCellHeight(for item: ModularTableViewItem) -> CGFloat { guard let item = item as? HomeCalendarCellItem else { return 0.0 } // cell height = (venues * venueHeight) + header + footer + cellInset - return (CGFloat(item.events?.count ?? 0) * UniversityNotificationCell.cellHeight) + HomeCellHeader.height + (Padding.pad * 3) + return (CGFloat(item.events.count) * UniversityNotificationCell.cellHeight) + HomeCellHeader.height + (Padding.pad * 3) } var events: [CalendarEvent]? diff --git a/PennMobile/Home/Cells/Calendar/HomeCalendarCellItem.swift b/PennMobile/Home/Cells/Calendar/HomeCalendarCellItem.swift index b485fb854..66f343382 100644 --- a/PennMobile/Home/Cells/Calendar/HomeCalendarCellItem.swift +++ b/PennMobile/Home/Cells/Calendar/HomeCalendarCellItem.swift @@ -15,20 +15,29 @@ final class HomeCalendarCellItem: HomeCellItem { return "calendar" } - var events: [CalendarEvent]? + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + CalendarAPI.instance.fetchCalendar({ events in + if let events = events { + completion([HomeCalendarCellItem(for: events)]) + } else { + completion([]) + } + }) + } - static func getItem(for json: JSON?) -> HomeCellItem? { - return HomeCalendarCellItem() + init(for events: [CalendarEvent]) { + self.events = events } + var events: [CalendarEvent] + static var associatedCell: ModularTableViewCell.Type { return HomeCalendarCell.self } func equals(item: ModularTableViewItem) -> Bool { guard let item = item as? HomeCalendarCellItem else { return false } - guard let events = events, let itemEvents = item.events else { return false } - return events == itemEvents + return events == item.events } } diff --git a/PennMobile/Home/Cells/Courses/HomeCoursesCellItem.swift b/PennMobile/Home/Cells/Courses/HomeCoursesCellItem.swift index d21bd651b..988a57c1c 100644 --- a/PennMobile/Home/Cells/Courses/HomeCoursesCellItem.swift +++ b/PennMobile/Home/Cells/Courses/HomeCoursesCellItem.swift @@ -10,11 +10,6 @@ import Foundation import SwiftyJSON final class HomeCoursesCellItem: HomeCellItem { - - static var jsonKey: String { - return "courses" - } - let weekday: String var courses: [Course] var isOnHomeScreen: Bool @@ -25,42 +20,34 @@ final class HomeCoursesCellItem: HomeCellItem { self.isOnHomeScreen = isOnHomeScreen } - static func getItem(for json: JSON?) -> HomeCellItem? { - if let json = json, let weekday = json["weekday"].string, let data: Data = try? json["courses"].rawData() { - // Courses provided by server - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase - do { - let courses = try decoder.decode([Course].self, from: data) - return HomeCoursesCellItem(weekday: weekday, courses: courses, isOnHomeScreen: true) - } catch { - return nil - } - } else if let courses = UserDefaults.standard.getCourses() { - // Courses not provided by server. Use courses saved on device. + static var jsonKey: String { + return "courses" + } + + static var associatedCell: ModularTableViewCell.Type { + return HomeCoursesCell.self + } + + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + if let courses = UserDefaults.standard.getCourses() { let coursesToday = courses.taughtToday if coursesToday.hasUpcomingCourse { let weekday = "Today" - return HomeCoursesCellItem(weekday: weekday, courses: Array(coursesToday), isOnHomeScreen: true) + return completion([HomeCoursesCellItem(weekday: weekday, courses: Array(coursesToday), isOnHomeScreen: true)]) } else { let weekday = "Tomorrow" let coursesTomorrow = courses.taughtTomorrow if !coursesTomorrow.isEmpty { - return HomeCoursesCellItem(weekday: weekday, courses: Array(coursesTomorrow), isOnHomeScreen: true) + return completion([HomeCoursesCellItem(weekday: weekday, courses: Array(coursesTomorrow), isOnHomeScreen: true)]) } else { - return nil + return completion([]) } } - } else { - return nil + return completion([]) } } - static var associatedCell: ModularTableViewCell.Type { - return HomeCoursesCell.self - } - func equals(item: ModularTableViewItem) -> Bool { guard let item = item as? HomeCoursesCellItem else { return false } return weekday == item.weekday diff --git a/PennMobile/Home/Cells/Dining/HomeDiningCellItem.swift b/PennMobile/Home/Cells/Dining/HomeDiningCellItem.swift index 230040317..2d79a0a7f 100644 --- a/PennMobile/Home/Cells/Dining/HomeDiningCellItem.swift +++ b/PennMobile/Home/Cells/Dining/HomeDiningCellItem.swift @@ -10,52 +10,32 @@ import Foundation import SwiftyJSON final class HomeDiningCellItem: HomeCellItem { + + static var jsonKey: String { + return "dining" + } + static var associatedCell: ModularTableViewCell.Type { return HomeDiningCell.self } var venues: [DiningVenue] - var venueIds: [Int] - init(venues: [DiningVenue], venueIds: [Int]) { + init(for venues: [DiningVenue]) { self.venues = venues - self.venueIds = venueIds } func equals(item: ModularTableViewItem) -> Bool { guard let item = item as? HomeDiningCellItem else { return false } return venues == item.venues } - - static var jsonKey: String { - return "dining" - } - - static func getItem(for json: JSON?) -> HomeCellItem? { - guard let json = json else { return nil } - return try? HomeDiningCellItem(json: json) - } -} - -// MARK: - JSON Parsing -extension HomeDiningCellItem { - convenience init(json: JSON) throws { - guard let ids = json["venues"].arrayObject as? [Int] else { - throw NetworkingError.jsonError - } - let venues: [DiningVenue] = DiningAPI.instance.getVenues(with: ids) - self.init(venues: venues, venueIds: ids) - } -} -// MARK: - API Fetching -extension HomeDiningCellItem: HomeAPIRequestable { - func fetchData(_ completion: @escaping () -> Void) { + static func getHomeCellItem(_ completion: @escaping((_ items: [HomeCellItem]) -> Void)) { DiningAPI.instance.fetchDiningHours { _ in - if self.venues.isEmpty { - self.venues = DiningAPI.instance.getVenues() - } - completion() + let venues = DiningAPI.instance.getVenues() + let instance = HomeDiningCellItem(for: venues) + + completion([instance]) } } } diff --git a/PennMobile/Home/Cells/Feature/HomeFeatureCellItem.swift b/PennMobile/Home/Cells/Feature/HomeFeatureCellItem.swift index ebdd46f90..8690a817c 100644 --- a/PennMobile/Home/Cells/Feature/HomeFeatureCellItem.swift +++ b/PennMobile/Home/Cells/Feature/HomeFeatureCellItem.swift @@ -10,6 +10,9 @@ import Foundation import SwiftyJSON final class HomeFeatureCellItem: HomeCellItem { + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + completion([]) + } static var jsonKey: String { return "feature" diff --git a/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift b/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift index 2d9c05e92..5b51f02a6 100644 --- a/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift +++ b/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift @@ -14,6 +14,10 @@ final class HomeGSRLocationsCellItem: HomeCellItem { return HomeGSRLocationsCell.self } + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + completion([]) + } + let locations: [GSRLocation] init(locations: [GSRLocation]) { diff --git a/PennMobile/Home/Cells/GSR Reservations/HomeReservationsCellItem.swift b/PennMobile/Home/Cells/GSR Reservations/HomeReservationsCellItem.swift index 665526743..17c215c59 100644 --- a/PennMobile/Home/Cells/GSR Reservations/HomeReservationsCellItem.swift +++ b/PennMobile/Home/Cells/GSR Reservations/HomeReservationsCellItem.swift @@ -14,31 +14,29 @@ final class HomeReservationsCellItem: HomeCellItem { return HomeReservationsCell.self } + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + GSRNetworkManager.instance.getReservations { result in + switch result { + case .success(let reservations): + completion([HomeReservationsCellItem(for: reservations)]) + case .failure: + completion([]) + } + } + } + var reservations: [GSRReservation] - init(reservations: [GSRReservation]) { + init(for reservations: [GSRReservation]) { self.reservations = reservations } func equals(item: ModularTableViewItem) -> Bool { guard let item = item as? HomeReservationsCellItem else { return false } - return reservations.count == item.reservations.count + return reservations == item.reservations } static var jsonKey: String { return "reservations" } - - static func getItem(for json: JSON?) -> HomeCellItem? { - guard let json = json else { return nil } - do { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - decoder.keyDecodingStrategy = .convertFromSnakeCase - let reservations = try decoder.decode([GSRReservation].self, from: json.rawData()) - return HomeReservationsCellItem(reservations: reservations) - } catch { - return nil - } - } } diff --git a/PennMobile/Home/Cells/GSR/HomeGSRCellItem.swift b/PennMobile/Home/Cells/GSR/HomeGSRCellItem.swift index 8a653ed8f..28cf893bb 100644 --- a/PennMobile/Home/Cells/GSR/HomeGSRCellItem.swift +++ b/PennMobile/Home/Cells/GSR/HomeGSRCellItem.swift @@ -10,6 +10,10 @@ import Foundation import SwiftyJSON final class HomeGSRCellItem: HomeCellItem { + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + completion([]) + } + static var associatedCell: ModularTableViewCell.Type { return HomeStudyRoomCell.self diff --git a/PennMobile/Home/Cells/Group Invites/HomeGroupInvitesCellItem.swift b/PennMobile/Home/Cells/Group Invites/HomeGroupInvitesCellItem.swift index 1bfe0732c..e584065dc 100644 --- a/PennMobile/Home/Cells/Group Invites/HomeGroupInvitesCellItem.swift +++ b/PennMobile/Home/Cells/Group Invites/HomeGroupInvitesCellItem.swift @@ -10,29 +10,21 @@ import Foundation import SwiftyJSON final class HomeGroupInvitesCellItem: HomeCellItem { - static var associatedCell: ModularTableViewCell.Type { - return HomeGroupInvitesCell.self - } + static var jsonKey = "invites" + static var associatedCell: ModularTableViewCell.Type = HomeGroupInvitesCell.self var invites: GSRGroupInvites - init(invites: GSRGroupInvites) { + init(for invites: GSRGroupInvites) { self.invites = invites } + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + completion([]) + } + func equals(item: ModularTableViewItem) -> Bool { guard let item = item as? HomeGroupInvitesCellItem else {return false} return invites.count == item.invites.count } - - static var jsonKey: String { - return "invites" - } - - static func getItem(for json: JSON?) -> HomeCellItem? { - // comment out following line once server is updated to show cell - guard let json = json else { return nil } - guard let invites = try? JSONDecoder().decode(GSRGroupInvites.self, from: json.rawData()) else { return nil } - return HomeGroupInvitesCellItem(invites: invites) - } } diff --git a/PennMobile/Home/Cells/Laundry/HomeLaundryCellItem.swift b/PennMobile/Home/Cells/Laundry/HomeLaundryCellItem.swift index a8931670c..701e16078 100644 --- a/PennMobile/Home/Cells/Laundry/HomeLaundryCellItem.swift +++ b/PennMobile/Home/Cells/Laundry/HomeLaundryCellItem.swift @@ -14,6 +14,10 @@ final class HomeLaundryCellItem: HomeCellItem { return HomeLaundryCell.self } + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + completion([]) + } + var room: LaundryRoom init(room: LaundryRoom) { diff --git a/PennMobile/Home/Cells/News/HomeNewsCell.swift b/PennMobile/Home/Cells/News/HomeNewsCell.swift index 4fee45165..bd4b4155a 100644 --- a/PennMobile/Home/Cells/News/HomeNewsCell.swift +++ b/PennMobile/Home/Cells/News/HomeNewsCell.swift @@ -92,7 +92,7 @@ final class HomeNewsCell: UITableViewCell, HomeCellConformable { extension HomeNewsCell { fileprivate func setupCell(with item: HomeNewsCellItem) { self.article = item.article - self.articleImageView.image = item.image + self.articleImageView.kf.setImage(with: URL(string: item.article.imageurl)) self.sourceLabel.text = article.source self.titleLabel.text = article.title self.subtitleLabel?.text = article.subtitle @@ -111,7 +111,7 @@ extension HomeNewsCell { @objc fileprivate func handleTapped(_ sender: Any) { guard let delegate = delegate as? URLSelectable else { return } FirebaseAnalyticsManager.shared.trackEvent(action: "News Cell Pressed", result: article.title, content: "") - delegate.handleUrlPressed(urlStr: article.articleUrl, title: article.source, item: self.item, shouldLog: true) + delegate.handleUrlPressed(urlStr: article.link, title: article.source, item: self.item, shouldLog: true) } } diff --git a/PennMobile/Home/Cells/News/HomeNewsCellItem.swift b/PennMobile/Home/Cells/News/HomeNewsCellItem.swift index 3159ee249..3692bf27e 100644 --- a/PennMobile/Home/Cells/News/HomeNewsCellItem.swift +++ b/PennMobile/Home/Cells/News/HomeNewsCellItem.swift @@ -7,59 +7,41 @@ // import Foundation -import SwiftyJSON final class HomeNewsCellItem: HomeCellItem { - - static var jsonKey: String { - return "news" - } + static var jsonKey = "news" + static var associatedCell: ModularTableViewCell.Type = HomeNewsCell.self let article: NewsArticle - var image: UIImage? var showSubtitle = false - init(article: NewsArticle) { + init(for article: NewsArticle) { self.article = article } - static func getItem(for json: JSON?) -> HomeCellItem? { - guard let json = json else { return nil } - return try? HomeNewsCellItem(json: json) - } - - static var associatedCell: ModularTableViewCell.Type { - return HomeNewsCell.self + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + let task = URLSession.shared.dataTask(with: URL(string: "https://studentlife.pennlabs.org/penndata/news/")!) { data, response, error in + guard let data = data else { completion([]); return } + + if let article = try? JSONDecoder().decode(NewsArticle.self, from: data) { + completion([HomeNewsCellItem(for: article)]) + } else { + completion([]) + } + } + + task.resume() } - + func equals(item: ModularTableViewItem) -> Bool { guard let item = item as? HomeNewsCellItem else { return false } return article.title == item.article.title } } -// MARK: - HomeAPIRequestable -extension HomeNewsCellItem: HomeAPIRequestable { - func fetchData(_ completion: @escaping () -> Void) { - ImageNetworkingManager.instance.downloadImage(imageUrl: article.imageUrl) { (image) in - self.image = image - completion() - } - } -} - -// MARK: - JSON Parsing -extension HomeNewsCellItem { - convenience init(json: JSON) throws { - let article = try NewsArticle(json: json) - self.init(article: article) - self.showSubtitle = json["show_subtitle"].boolValue - } -} - // MARK: - Logging ID extension HomeNewsCellItem: LoggingIdentifiable { var id: String { - return article.articleUrl + return article.link } } diff --git a/PennMobile/Home/Cells/News/NewsArticle.swift b/PennMobile/Home/Cells/News/NewsArticle.swift index e139e74b2..b0b367fe0 100644 --- a/PennMobile/Home/Cells/News/NewsArticle.swift +++ b/PennMobile/Home/Cells/News/NewsArticle.swift @@ -7,38 +7,12 @@ // import Foundation -import SwiftyJSON -class NewsArticle { +struct NewsArticle: Codable { let source: String + let link: String let title: String let subtitle: String let timestamp: String - let imageUrl: String - let articleUrl: String - - init(source: String, title: String, subtitle: String, timestamp: String, imageUrl: String, articleUrl: String) { - self.source = source - self.title = title - self.subtitle = subtitle - self.timestamp = timestamp - self.imageUrl = imageUrl - self.articleUrl = articleUrl - } -} - -// MARK: - JSON Parsing -extension NewsArticle { - convenience init(json: JSON) throws { - guard let source = json["source"].string, - let title = json["title"].string, - let subtitle = json["subtitle"].string, - let timestamp = json["timestamp"].string, - let imageUrl = json["image_url"].string, - let articleUrl = json["article_url"].string else { - throw NetworkingError.jsonError - } - - self.init(source: source, title: title, subtitle: subtitle, timestamp: timestamp, imageUrl: imageUrl, articleUrl: articleUrl) - } + let imageurl: String } diff --git a/PennMobile/Home/Cells/Post/HomePostCellItem.swift b/PennMobile/Home/Cells/Post/HomePostCellItem.swift index a4c6cd52d..adbdcc010 100644 --- a/PennMobile/Home/Cells/Post/HomePostCellItem.swift +++ b/PennMobile/Home/Cells/Post/HomePostCellItem.swift @@ -10,6 +10,9 @@ import Foundation import SwiftyJSON final class HomePostCellItem: HomeCellItem { + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + completion([]) + } static var jsonKey: String { return "post" diff --git a/PennMobile/Home/Controllers/HomeViewController.swift b/PennMobile/Home/Controllers/HomeViewController.swift index 3c27a5745..7a430cf66 100644 --- a/PennMobile/Home/Controllers/HomeViewController.swift +++ b/PennMobile/Home/Controllers/HomeViewController.swift @@ -174,32 +174,17 @@ extension HomeViewController { // MARK: - Networking extension HomeViewController { - func fetchViewModel(_ secondAttempt: Bool = false, _ completion: @escaping () -> Void) { - HomeAPIService.instance.fetchModel { (model, error) in - DispatchQueue.main.async { - if error != nil { - let navigationVC = self.navigationController as? HomeNavigationController - - if !secondAttempt { - self.fetchViewModel(true, completion) - } else { - navigationVC?.addStatusBar(text: .apiError) - completion() - } - return - } - - guard let model = model else { return } - self.setModel(model) - UIView.transition(with: self.tableView, - duration: 0.35, - options: .transitionCrossDissolve, - animations: { self.tableView.reloadData() }) - self.fetchAllCellData { - // Do anything here that needs to be done after all the cells are loaded - } - completion() - } + func fetchViewModel(_ completion: @escaping () -> Void) { + HomeAPIService.instance.fetchModel { model in + print(model.items.count) + self.setModel(model) + + UIView.transition(with: self.tableView, + duration: 0.35, + options: .transitionCrossDissolve, + animations: { self.tableView.reloadData() }) + + completion() } } diff --git a/PennMobile/Home/Model/HomeCellItem.swift b/PennMobile/Home/Model/HomeCellItem.swift index b8d5512eb..820525ff6 100644 --- a/PennMobile/Home/Model/HomeCellItem.swift +++ b/PennMobile/Home/Model/HomeCellItem.swift @@ -11,7 +11,7 @@ import SwiftyJSON protocol HomeCellItem: ModularTableViewItem { static var jsonKey: String { get } - static func getItem(for json: JSON?) -> HomeCellItem? + static func getHomeCellItem(_ completion: @escaping((_ item: [HomeCellItem]) -> Void)) } protocol LoggingIdentifiable where Self: HomeCellItem { diff --git a/PennMobile/Home/Networking/HomeAPIService.swift b/PennMobile/Home/Networking/HomeAPIService.swift index 2d2b449bd..862fb0ac4 100644 --- a/PennMobile/Home/Networking/HomeAPIService.swift +++ b/PennMobile/Home/Networking/HomeAPIService.swift @@ -9,85 +9,29 @@ import Foundation import SwiftyJSON -final class HomeAPIService: Requestable { +final class HomeAPIService { static let instance = HomeAPIService() - private init() {} - func fetchModel(_ completion: @escaping (_ model: HomeTableViewModel?, _ error: NetworkingError?) -> Void) { - let version = UserDefaults.standard.getAppVersion() - var url = "https://api.pennlabs.org/homepage?version=\(version)" - if let sessionID = GSRUser.getSessionID() { - url = "\(url)&sessionid=\(sessionID)" - } - if let courses = UserDefaults.standard.getCourses(), !courses.enrolledIn.isEmpty { - if courses.taughtToday.hasUpcomingCourse { - url = "\(url)&hasCourses=today" - } else if !courses.taughtTomorrow.isEmpty { - url = "\(url)&hasCourses=tomorrow" - } - } - - url = "\(url)&groupsEnabled=\(UserDefaults.standard.gsrGroupsEnabled())" + func fetchModel(_ completion: @escaping ((HomeTableViewModel) -> Void)) { + let group = DispatchGroup() - OAuth2NetworkManager.instance.getAccessToken { (token) in - // Make request without access token if one does not exist - let url = URL(string: url)! - var request = token != nil ? URLRequest(url: url, accessToken: token!) : URLRequest(url: url) - - // Add device ID to request to access data associated associated with device id (ex: favorite dining halls) - let deviceID = getDeviceID() - request.setValue(deviceID, forHTTPHeaderField: "X-Device-ID") - - let task = URLSession.shared.dataTask(with: request) { (data, response, error) in - if let error = error, (error as NSError).code == -1009 { - completion(nil, NetworkingError.noInternet) - return - } - if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { - completion(nil, NetworkingError.serverError) - return - } - var model: HomeTableViewModel? = HomeTableViewModel() - var error: NetworkingError? = NetworkingError.jsonError - if let data = data { - let json = JSON(data) - model = try? HomeTableViewModel(json: json) - if model != nil { - error = nil - } + let model = HomeTableViewModel() + + // Fetch HomeCellItem for all HomeItemTypes + for item in HomeItemTypes.instance.getAllTypes() { + group.enter() + item.getHomeCellItem { item in + item.forEach { i in + model.items.append(i) } - completion(model, error) - } - task.resume() - } - } -} - -extension HomeTableViewModel { - convenience init(json: JSON) throws { - self.init() - - guard let cellsJSON = json["cells"].array else { - throw NetworkingError.jsonError - } - - self.items = [HomeCellItem]() - - // Initialize default items for development - // Note: this should be empty in production - for ItemType in HomeItemTypes.instance.getDefaultItems() { - if let item = ItemType.getItem(for: nil) { - items.append(item) + + group.leave() } } - - // Initialize items from JSON - for json in cellsJSON { - let type = json["type"].stringValue - let infoJSON = json["info"] - if let ItemType = HomeItemTypes.instance.getItemType(for: type), let item = ItemType.getItem(for: infoJSON) { - items.append(item) - } + + // Handle completion of model after it is done + group.notify(queue: .main) { + completion(model) } } } From 1a6dadbe9c0dfd06cbdde0e76d585f5acccbb5c2 Mon Sep 17 00:00:00 2001 From: Samantha Su Date: Tue, 9 Nov 2021 18:44:39 -0500 Subject: [PATCH 02/21] Penn Events row in More --- PennMobile.xcodeproj/project.pbxproj | 56 +++++---- PennMobile/Events/EventsAPI.swift | 52 ++++++++ PennMobile/Events/EventsTableViewCell.swift | 113 ++++++++++++++++++ PennMobile/Events/EventsViewController.swift | 98 +++++++++++++++ PennMobile/Events/PennEvents.swift | 43 +++++++ PennMobile/General/Extensions.swift | 12 ++ .../Setup + Navigation/ControllerModel.swift | 6 +- Podfile.lock | 2 +- 8 files changed, 357 insertions(+), 25 deletions(-) create mode 100644 PennMobile/Events/EventsAPI.swift create mode 100644 PennMobile/Events/EventsTableViewCell.swift create mode 100644 PennMobile/Events/EventsViewController.swift create mode 100644 PennMobile/Events/PennEvents.swift diff --git a/PennMobile.xcodeproj/project.pbxproj b/PennMobile.xcodeproj/project.pbxproj index ca2b69672..f10c7cc33 100644 --- a/PennMobile.xcodeproj/project.pbxproj +++ b/PennMobile.xcodeproj/project.pbxproj @@ -181,6 +181,10 @@ 6CE12F9226E82DC600284D9F /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = 6CE12F9126E82DC600284D9F /* FirebaseCrashlytics */; }; 6CFA06F626E8352F00944B8E /* HomeStudyRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFA06F526E8352F00944B8E /* HomeStudyRoomCell.swift */; }; 6CFA06F826E8355400944B8E /* HomeFeatureCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFA06F726E8355400944B8E /* HomeFeatureCellItem.swift */; }; + 8523A2902707B0FB0052F608 /* EventsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8523A28F2707B0FB0052F608 /* EventsViewController.swift */; }; + 8523A2952707B31E0052F608 /* EventsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8523A2942707B31E0052F608 /* EventsTableViewCell.swift */; }; + 8523A29F2707B68B0052F608 /* EventsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8523A29E2707B68B0052F608 /* EventsAPI.swift */; }; + 8523A2AF2707B88A0052F608 /* PennEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8523A2AE2707B88A0052F608 /* PennEvents.swift */; }; 97AA806923D26BC700C23488 /* DiningBalanceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97AA806423D26BC700C23488 /* DiningBalanceCell.swift */; }; 97AA806A23D26BC700C23488 /* TransactionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97AA806523D26BC700C23488 /* TransactionTableViewCell.swift */; }; 97AA806B23D26BC700C23488 /* DiningHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97AA806623D26BC700C23488 /* DiningHeaderView.swift */; }; @@ -514,6 +518,10 @@ 6CFA06F526E8352F00944B8E /* HomeStudyRoomCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeStudyRoomCell.swift; sourceTree = ""; }; 6CFA06F726E8355400944B8E /* HomeFeatureCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeFeatureCellItem.swift; sourceTree = ""; }; 72787B1E070BFCDF84D8C3CA /* Pods-PennMobile.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PennMobile.debug.xcconfig"; path = "Target Support Files/Pods-PennMobile/Pods-PennMobile.debug.xcconfig"; sourceTree = ""; }; + 8523A28F2707B0FB0052F608 /* EventsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsViewController.swift; sourceTree = ""; }; + 8523A2942707B31E0052F608 /* EventsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsTableViewCell.swift; sourceTree = ""; }; + 8523A29E2707B68B0052F608 /* EventsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsAPI.swift; sourceTree = ""; }; + 8523A2AE2707B88A0052F608 /* PennEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PennEvents.swift; sourceTree = ""; }; 97AA806423D26BC700C23488 /* DiningBalanceCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiningBalanceCell.swift; sourceTree = ""; }; 97AA806523D26BC700C23488 /* TransactionTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionTableViewCell.swift; sourceTree = ""; }; 97AA806623D26BC700C23488 /* DiningHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiningHeaderView.swift; sourceTree = ""; }; @@ -827,6 +835,7 @@ 21D5E06523B68F0E00B331CC /* Housing */, 212304C52054ED9900958CE0 /* Spring Fling */, 216640D31EBB80FD00746B8E /* News */, + 8523A28E2707B0790052F608 /* Events */, 216640EE1EBB867600746B8E /* Contacts */, CF1CE9B8205D5BF100C51F46 /* More Tab */, 216640831EBADB7000746B8E /* Supporting_Files */, @@ -1301,6 +1310,17 @@ name = Frameworks; sourceTree = ""; }; + 8523A28E2707B0790052F608 /* Events */ = { + isa = PBXGroup; + children = ( + 8523A28F2707B0FB0052F608 /* EventsViewController.swift */, + 8523A2942707B31E0052F608 /* EventsTableViewCell.swift */, + 8523A29E2707B68B0052F608 /* EventsAPI.swift */, + 8523A2AE2707B88A0052F608 /* PennEvents.swift */, + ); + path = Events; + sourceTree = ""; + }; 97AA806323D26BC700C23488 /* Views */ = { isa = PBXGroup; children = ( @@ -1865,13 +1885,12 @@ buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-AutomatedScreenshotUITests/Pods-AutomatedScreenshotUITests-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/SimulatorStatusMagic/SimulatorStatusMagic.framework", + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-AutomatedScreenshotUITests/Pods-AutomatedScreenshotUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SimulatorStatusMagic.framework", + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-AutomatedScreenshotUITests/Pods-AutomatedScreenshotUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1922,23 +1941,12 @@ buildActionMask = 12; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PennMobile/Pods-PennMobile-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Base32/Base32.framework", - "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework", - "${BUILT_PRODUCTS_DIR}/OneTimePassword/OneTimePassword.framework", - "${BUILT_PRODUCTS_DIR}/SCLAlertView/SCLAlertView.framework", - "${BUILT_PRODUCTS_DIR}/ScrollableGraphView/ScrollableGraphView.framework", - "${BUILT_PRODUCTS_DIR}/XLPagerTabStrip/XLPagerTabStrip.framework", + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PennMobile/Pods-PennMobile-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Base32.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OneTimePassword.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SCLAlertView.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ScrollableGraphView.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/XLPagerTabStrip.framework", + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PennMobile/Pods-PennMobile-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -2007,6 +2015,7 @@ 21D5E06723B68F3100B331CC /* Model.swift in Sources */, F23CC0BF235E3F610007317A /* DiningVenue+Codable.swift in Sources */, F299616F23F78D9B00C67A3B /* AppDelegate+NotificationExtension.swift in Sources */, + 8523A2AF2707B88A0052F608 /* PennEvents.swift in Sources */, 6CAE4387253370E600BD0200 /* PredictionsGraphView+SmoothedData.swift in Sources */, 6CAE438B253370E600BD0200 /* VariableStepLineGraphView.swift in Sources */, 21B8BBB82223963C00EBC1D0 /* Degree.swift in Sources */, @@ -2042,6 +2051,7 @@ B654BAEE1F801F830038B9D5 /* LaundryTableViewController.swift in Sources */, 6C6035F526E722890025FBC7 /* MenuDisclosureGroup.swift in Sources */, F2F4C92023611EB900816456 /* Colors.swift in Sources */, + 8523A29F2707B68B0052F608 /* EventsAPI.swift in Sources */, 6CAE4380253370E500BD0200 /* FrequentLocationsView.swift in Sources */, F27AA01823BC6CEC00276C4F /* PermissionView.swift in Sources */, 6CAE43C62533731000BD0200 /* DiningInsightsAPIResponse.swift in Sources */, @@ -2177,6 +2187,7 @@ 21508166220D2499002F7EA1 /* HomeNewsCellItem.swift in Sources */, 2189C0912027CE2E00771C1F /* GSRViewModel.swift in Sources */, C15C4B4E223EB16F00E443FD /* HomeReservationsCell.swift in Sources */, + 8523A2902707B0FB0052F608 /* EventsViewController.swift in Sources */, B62875F92118F95300FB2873 /* BuildingProtocol.swift in Sources */, F212BE8623B6DA8D00ED46A1 /* PrivacyTableViewCell.swift in Sources */, 66E8ECA72381CB5100945BEA /* TwoFactorTokenGenerator.swift in Sources */, @@ -2201,6 +2212,7 @@ F2562A3525583C5C0021C92F /* ButtonWithImage.swift in Sources */, 97E79E102100DD5000D3D606 /* FitnessHourCell.swift in Sources */, EFE2D6FB239B12270020F6BF /* GSRGroupNewIntialController.swift in Sources */, + 8523A2952707B31E0052F608 /* EventsTableViewCell.swift in Sources */, 21ECADA11F2FFE0600569883 /* UserDefaults + Helpers.swift in Sources */, 97E79E142100E1FA00D3D606 /* FitnessViewController.swift in Sources */, 21B556172224FDC500D80F61 /* RootViewController.swift in Sources */, @@ -2408,7 +2420,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CF_BUNDLE_LONG_VERSION_STRING"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = VU59R57FGM; + DEVELOPMENT_TEAM = ""; EXCLUDED_ARCHS = ""; GCC_PREFIX_HEADER = PennMobile/Supporting_Files/PrefixHeader.pch; INFOPLIST_FILE = PennMobile/Supporting_Files/Info.plist; @@ -2441,7 +2453,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CF_BUNDLE_LONG_VERSION_STRING"; - DEVELOPMENT_TEAM = VU59R57FGM; + DEVELOPMENT_TEAM = ""; EXCLUDED_ARCHS = ""; GCC_PREFIX_HEADER = PennMobile/Supporting_Files/PrefixHeader.pch; INFOPLIST_FILE = PennMobile/Supporting_Files/Info.plist; diff --git a/PennMobile/Events/EventsAPI.swift b/PennMobile/Events/EventsAPI.swift new file mode 100644 index 000000000..572d52897 --- /dev/null +++ b/PennMobile/Events/EventsAPI.swift @@ -0,0 +1,52 @@ +// +// EventsAPI.swift +// PennMobile +// +// Created by Samantha Su on 10/1/21. +// Copyright © 2021 PennLabs. All rights reserved. +// + +import SwiftyJSON +import Foundation + +class EventsAPI: Requestable { + static let instance = EventsAPI() + + let eventsUrl = "https://penntoday.upenn.edu/events-feed?_format=json" + + func fetchEvents(_ completion: @escaping (_ result: Result<[PennEvents], NetworkingError>) -> Void) { + getRequestData(url: eventsUrl) { (data, error, statusCode) in + if statusCode == nil { + return completion(.failure(.noInternet)) + } + + if statusCode != 200 { + return completion(.failure(.serverError)) + } + + guard let data = data else { return completion(.failure(.other)) } + + let decoder = JSONDecoder() + + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(abbreviation: "EST") + formatter.dateFormat = "MM/dd/yyyy" + + decoder.dateDecodingStrategy = .formatted(formatter) + + do { + let events = try decoder.decode([PennEvents].self, from: data) + self.saveToCache(events) + completion(.success(events)) + } catch { + print(error) + } + } + } + + // MARK: - Cache Methods + func saveToCache(_ response: [PennEvents]) { + Storage.store(response, to: .caches, as: PennEvents.directory) + } +} diff --git a/PennMobile/Events/EventsTableViewCell.swift b/PennMobile/Events/EventsTableViewCell.swift new file mode 100644 index 000000000..e577e7f5d --- /dev/null +++ b/PennMobile/Events/EventsTableViewCell.swift @@ -0,0 +1,113 @@ +// +// EventsTableViewCell.swift +// PennMobile +// +// Created by Samantha Su on 10/1/21. +// Copyright © 2021 PennLabs. All rights reserved. +// + +import UIKit +import Kingfisher +import SwiftSoup + +class EventsTableViewCell: UITableViewCell { + + lazy var imageExistsConstraint = eventImageView.widthAnchor.constraint(equalToConstant:120) + lazy var imageMissingConstraint = eventImageView.widthAnchor.constraint(equalToConstant:0) + var isExpanded = false + + var pennEvent: PennEvents?{ + didSet{ + guard let event = pennEvent else {return} + if event.media_image == ""{ + imageExistsConstraint.isActive = false + imageMissingConstraint.isActive = true + }else{ + let imageString = "https://penntoday.upenn.edu" + (event.media_image.slice(from: " Int { + return events.count + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return UITableView.automaticDimension + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "EventsTableViewCell", for: indexPath) as! EventsTableViewCell + cell.pennEvent = events[indexPath.row] + return cell + } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + // 1 + guard let cell = tableView.cellForRow(at: indexPath) as? EventsTableViewCell else { + return + } + + // 2 + cell.isExpanded = !cell.isExpanded + + // 3 + cell.bodyLabel.numberOfLines = cell.isExpanded ? 3: 0 + + // 4 + tableView.beginUpdates() + tableView.endUpdates() + } +} diff --git a/PennMobile/Events/PennEvents.swift b/PennMobile/Events/PennEvents.swift new file mode 100644 index 000000000..c33c4adc4 --- /dev/null +++ b/PennMobile/Events/PennEvents.swift @@ -0,0 +1,43 @@ +// +// PennEvents.swift +// PennMobile +// +// Created by Samantha Su on 10/1/21. +// Copyright © 2021 PennLabs. All rights reserved. +// + +import Foundation + +struct PennEvents: Codable { + static let directory = "events.json" + + let id: String + let title: String + let body: String + let image: String + let location: StringOrBool + let category: String + let path: String + let start: Date? + let end: Date? + let starttime: String + let endtime: String + let allday: String + var media_image: String + let shortdate: String + + var isAllDay: Bool { + return allday == "All day" + } +} + +struct StringOrBool: Codable{ + var value: String? + init(from decoder: Decoder) throws { + if let string = try? String(from: decoder) { + value = string + return + } + value = nil + } +} diff --git a/PennMobile/General/Extensions.swift b/PennMobile/General/Extensions.swift index c81988c88..3c7153c24 100755 --- a/PennMobile/General/Extensions.swift +++ b/PennMobile/General/Extensions.swift @@ -473,6 +473,18 @@ extension String { } } +//slicing Penn Events API image source urls +extension String { + //https://stackoverflow.com/questions/31725424/swift-get-string-between-2-strings-in-a-string + func slice(from: String, to: String) -> String? { + return (range(of: from)?.upperBound).flatMap { substringFrom in + (range(of: to, range: substringFrom.. NSMutableAttributedString { let attrs: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: size, weight: .bold)] diff --git a/PennMobile/Setup + Navigation/ControllerModel.swift b/PennMobile/Setup + Navigation/ControllerModel.swift index 8928eb3cd..ba18e9e3f 100644 --- a/PennMobile/Setup + Navigation/ControllerModel.swift +++ b/PennMobile/Setup + Navigation/ControllerModel.swift @@ -22,6 +22,7 @@ enum Feature: String { case map = "Map" case news = "News" case contacts = "Penn Contacts" + case events = "Penn Events" case about = "About" case fling = "Spring Fling" case event = "Event" @@ -58,6 +59,7 @@ class ControllerModel: NSObject { vcDictionary[.courseSchedule] = CourseScheduleViewController() vcDictionary[.pacCode] = PacCodeViewController() vcDictionary[.courseAlerts] = CourseAlertController() + vcDictionary[.events] = EventsTableViewController() //vcDictionary[.fitness] = FitnessViewController() //vcDictionary[.fling] = FlingViewController() } @@ -81,7 +83,7 @@ class ControllerModel: NSObject { //keeping this #if DEBUG in case we want to remove course alerts from production //courseAlerts should only show up in testflight but we should NEVER show in production, need to manually remove it in the future #if DEBUG - return [.news, .contacts, .courseSchedule, .courseAlerts, .about] + return [.news, .contacts, .courseSchedule, .courseAlerts, .events, .about] #else return [.news, .contacts, .courseSchedule, .about] #endif @@ -93,7 +95,7 @@ class ControllerModel: NSObject { //courseAlerts should only show up in testflight but we should NEVER show in production, need to manually remove it in the future get { #if DEBUG - return [#imageLiteral(resourceName: "News"), #imageLiteral(resourceName: "Contacts"), #imageLiteral(resourceName: "Calendar Light"), #imageLiteral(resourceName: "PCA"), #imageLiteral(resourceName: "logo-small")] + return [#imageLiteral(resourceName: "News"), #imageLiteral(resourceName: "Contacts"), #imageLiteral(resourceName: "Calendar Light"), #imageLiteral(resourceName: "PCA"), #imageLiteral(resourceName: "PCA"), #imageLiteral(resourceName: "logo-small")] #else return [#imageLiteral(resourceName: "News"), #imageLiteral(resourceName: "Contacts"), #imageLiteral(resourceName: "Calendar Light"), #imageLiteral(resourceName: "PCA"), #imageLiteral(resourceName: "logo-small")] #endif diff --git a/Podfile.lock b/Podfile.lock index 14376c527..cf643f1ce 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -37,4 +37,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 9e5e28b991a790112963ed77c95535421e3a89ff -COCOAPODS: 1.10.1 +COCOAPODS: 1.9.2 From 8e42c32efb7c0c641882b688f06197d6e52d0754 Mon Sep 17 00:00:00 2001 From: Samantha Su Date: Fri, 12 Nov 2021 17:04:46 -0500 Subject: [PATCH 03/21] cleaned up comments, renamed files, fixed style issues --- PennMobile.xcodeproj/project.pbxproj | 16 ++++++------ PennMobile/Events/EventsAPI.swift | 7 +++--- ...ll.swift => PennEventsTableViewCell.swift} | 15 ++++------- ...ft => PennEventsTableViewController.swift} | 25 ++++++------------- .../Setup + Navigation/ControllerModel.swift | 2 +- 5 files changed, 24 insertions(+), 41 deletions(-) rename PennMobile/Events/{EventsTableViewCell.swift => PennEventsTableViewCell.swift} (93%) rename PennMobile/Events/{EventsViewController.swift => PennEventsTableViewController.swift} (83%) diff --git a/PennMobile.xcodeproj/project.pbxproj b/PennMobile.xcodeproj/project.pbxproj index f10c7cc33..563a80209 100644 --- a/PennMobile.xcodeproj/project.pbxproj +++ b/PennMobile.xcodeproj/project.pbxproj @@ -181,8 +181,8 @@ 6CE12F9226E82DC600284D9F /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = 6CE12F9126E82DC600284D9F /* FirebaseCrashlytics */; }; 6CFA06F626E8352F00944B8E /* HomeStudyRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFA06F526E8352F00944B8E /* HomeStudyRoomCell.swift */; }; 6CFA06F826E8355400944B8E /* HomeFeatureCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFA06F726E8355400944B8E /* HomeFeatureCellItem.swift */; }; - 8523A2902707B0FB0052F608 /* EventsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8523A28F2707B0FB0052F608 /* EventsViewController.swift */; }; - 8523A2952707B31E0052F608 /* EventsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8523A2942707B31E0052F608 /* EventsTableViewCell.swift */; }; + 8523A2902707B0FB0052F608 /* PennEventsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8523A28F2707B0FB0052F608 /* PennEventsTableViewController.swift */; }; + 8523A2952707B31E0052F608 /* PennEventsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8523A2942707B31E0052F608 /* PennEventsTableViewCell.swift */; }; 8523A29F2707B68B0052F608 /* EventsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8523A29E2707B68B0052F608 /* EventsAPI.swift */; }; 8523A2AF2707B88A0052F608 /* PennEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8523A2AE2707B88A0052F608 /* PennEvents.swift */; }; 97AA806923D26BC700C23488 /* DiningBalanceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97AA806423D26BC700C23488 /* DiningBalanceCell.swift */; }; @@ -518,8 +518,8 @@ 6CFA06F526E8352F00944B8E /* HomeStudyRoomCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeStudyRoomCell.swift; sourceTree = ""; }; 6CFA06F726E8355400944B8E /* HomeFeatureCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeFeatureCellItem.swift; sourceTree = ""; }; 72787B1E070BFCDF84D8C3CA /* Pods-PennMobile.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PennMobile.debug.xcconfig"; path = "Target Support Files/Pods-PennMobile/Pods-PennMobile.debug.xcconfig"; sourceTree = ""; }; - 8523A28F2707B0FB0052F608 /* EventsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsViewController.swift; sourceTree = ""; }; - 8523A2942707B31E0052F608 /* EventsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsTableViewCell.swift; sourceTree = ""; }; + 8523A28F2707B0FB0052F608 /* PennEventsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PennEventsTableViewController.swift; sourceTree = ""; }; + 8523A2942707B31E0052F608 /* PennEventsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PennEventsTableViewCell.swift; sourceTree = ""; }; 8523A29E2707B68B0052F608 /* EventsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsAPI.swift; sourceTree = ""; }; 8523A2AE2707B88A0052F608 /* PennEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PennEvents.swift; sourceTree = ""; }; 97AA806423D26BC700C23488 /* DiningBalanceCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiningBalanceCell.swift; sourceTree = ""; }; @@ -1313,8 +1313,8 @@ 8523A28E2707B0790052F608 /* Events */ = { isa = PBXGroup; children = ( - 8523A28F2707B0FB0052F608 /* EventsViewController.swift */, - 8523A2942707B31E0052F608 /* EventsTableViewCell.swift */, + 8523A28F2707B0FB0052F608 /* PennEventsTableViewController.swift */, + 8523A2942707B31E0052F608 /* PennEventsTableViewCell.swift */, 8523A29E2707B68B0052F608 /* EventsAPI.swift */, 8523A2AE2707B88A0052F608 /* PennEvents.swift */, ); @@ -2187,7 +2187,7 @@ 21508166220D2499002F7EA1 /* HomeNewsCellItem.swift in Sources */, 2189C0912027CE2E00771C1F /* GSRViewModel.swift in Sources */, C15C4B4E223EB16F00E443FD /* HomeReservationsCell.swift in Sources */, - 8523A2902707B0FB0052F608 /* EventsViewController.swift in Sources */, + 8523A2902707B0FB0052F608 /* PennEventsTableViewController.swift in Sources */, B62875F92118F95300FB2873 /* BuildingProtocol.swift in Sources */, F212BE8623B6DA8D00ED46A1 /* PrivacyTableViewCell.swift in Sources */, 66E8ECA72381CB5100945BEA /* TwoFactorTokenGenerator.swift in Sources */, @@ -2212,7 +2212,7 @@ F2562A3525583C5C0021C92F /* ButtonWithImage.swift in Sources */, 97E79E102100DD5000D3D606 /* FitnessHourCell.swift in Sources */, EFE2D6FB239B12270020F6BF /* GSRGroupNewIntialController.swift in Sources */, - 8523A2952707B31E0052F608 /* EventsTableViewCell.swift in Sources */, + 8523A2952707B31E0052F608 /* PennEventsTableViewCell.swift in Sources */, 21ECADA11F2FFE0600569883 /* UserDefaults + Helpers.swift in Sources */, 97E79E142100E1FA00D3D606 /* FitnessViewController.swift in Sources */, 21B556172224FDC500D80F61 /* RootViewController.swift in Sources */, diff --git a/PennMobile/Events/EventsAPI.swift b/PennMobile/Events/EventsAPI.swift index 572d52897..177f3285e 100644 --- a/PennMobile/Events/EventsAPI.swift +++ b/PennMobile/Events/EventsAPI.swift @@ -35,12 +35,11 @@ class EventsAPI: Requestable { decoder.dateDecodingStrategy = .formatted(formatter) - do { - let events = try decoder.decode([PennEvents].self, from: data) + if let events = try? decoder.decode([PennEvents].self, from: data){ self.saveToCache(events) completion(.success(events)) - } catch { - print(error) + } else { + completion(.failure(.serverError)) } } } diff --git a/PennMobile/Events/EventsTableViewCell.swift b/PennMobile/Events/PennEventsTableViewCell.swift similarity index 93% rename from PennMobile/Events/EventsTableViewCell.swift rename to PennMobile/Events/PennEventsTableViewCell.swift index e577e7f5d..aca881602 100644 --- a/PennMobile/Events/EventsTableViewCell.swift +++ b/PennMobile/Events/PennEventsTableViewCell.swift @@ -10,19 +10,19 @@ import UIKit import Kingfisher import SwiftSoup -class EventsTableViewCell: UITableViewCell { +class PennEventsTableViewCell: UITableViewCell { lazy var imageExistsConstraint = eventImageView.widthAnchor.constraint(equalToConstant:120) lazy var imageMissingConstraint = eventImageView.widthAnchor.constraint(equalToConstant:0) var isExpanded = false - var pennEvent: PennEvents?{ - didSet{ + var pennEvent: PennEvents? { + didSet { guard let event = pennEvent else {return} - if event.media_image == ""{ + if event.media_image == "" { imageExistsConstraint.isActive = false imageMissingConstraint.isActive = true - }else{ + } else { let imageString = "https://penntoday.upenn.edu" + (event.media_image.slice(from: " Int { return events.count } @@ -75,23 +73,14 @@ extension EventsTableViewController{ } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "EventsTableViewCell", for: indexPath) as! EventsTableViewCell + let cell = tableView.dequeueReusableCell(withIdentifier: "PennEventsTableViewCell", for: indexPath) as! PennEventsTableViewCell cell.pennEvent = events[indexPath.row] return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - // 1 - guard let cell = tableView.cellForRow(at: indexPath) as? EventsTableViewCell else { - return - } - - // 2 + guard let cell = tableView.cellForRow(at: indexPath) as? PennEventsTableViewCell else {return} cell.isExpanded = !cell.isExpanded - - // 3 cell.bodyLabel.numberOfLines = cell.isExpanded ? 3: 0 - - // 4 tableView.beginUpdates() tableView.endUpdates() } diff --git a/PennMobile/Setup + Navigation/ControllerModel.swift b/PennMobile/Setup + Navigation/ControllerModel.swift index ba18e9e3f..1ef4088ef 100644 --- a/PennMobile/Setup + Navigation/ControllerModel.swift +++ b/PennMobile/Setup + Navigation/ControllerModel.swift @@ -59,7 +59,7 @@ class ControllerModel: NSObject { vcDictionary[.courseSchedule] = CourseScheduleViewController() vcDictionary[.pacCode] = PacCodeViewController() vcDictionary[.courseAlerts] = CourseAlertController() - vcDictionary[.events] = EventsTableViewController() + vcDictionary[.events] = PennEventsTableViewController() //vcDictionary[.fitness] = FitnessViewController() //vcDictionary[.fling] = FlingViewController() } From 4e0d13c9886444c6c55c8b91f0cf9f6a851b5bda Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Sun, 14 Nov 2021 12:29:34 -0500 Subject: [PATCH 04/21] Initial changes for home screen --- PennMobile.xcodeproj/project.pbxproj | 12 +++ .../GSR-Booking/Model/GSRReservation.swift | 2 +- .../Cells/Calendar/HomeCalendarCell.swift | 2 +- .../Cells/Calendar/HomeCalendarCellItem.swift | 19 ++-- .../Cells/Courses/HomeCoursesCellItem.swift | 41 +++------ .../Cells/Dining/HomeDiningCellItem.swift | 42 +++------ .../Cells/Feature/HomeFeatureCellItem.swift | 3 + .../HomeGSRLocationsCellItem.swift | 40 ++++++--- .../HomeReservationsCellItem.swift | 32 +++---- .../Home/Cells/GSR/HomeGSRCellItem.swift | 3 + .../HomeGroupInvitesCellItem.swift | 22 ++--- .../Cells/Laundry/HomeLaundryCellItem.swift | 4 + PennMobile/Home/Cells/News/HomeNewsCell.swift | 4 +- .../Home/Cells/News/HomeNewsCellItem.swift | 52 ++++------- PennMobile/Home/Cells/News/NewsArticle.swift | 32 +------ .../Home/Cells/Post/HomePostCellItem.swift | 10 +-- .../Home/Controllers/HomeViewController.swift | 36 +++----- PennMobile/Home/Model/HomeCellItem.swift | 6 +- PennMobile/Home/Model/HomeItemTypes.swift | 1 + .../Home/Model/HomeTableViewModel.swift | 10 +-- .../Home/Networking/HomeAPIService.swift | 90 ++++--------------- 21 files changed, 170 insertions(+), 293 deletions(-) diff --git a/PennMobile.xcodeproj/project.pbxproj b/PennMobile.xcodeproj/project.pbxproj index 763e1d045..fb7ee2988 100644 --- a/PennMobile.xcodeproj/project.pbxproj +++ b/PennMobile.xcodeproj/project.pbxproj @@ -142,6 +142,7 @@ 66E8ECA52381CAA900945BEA /* TOTPFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E8ECA42381CAA800945BEA /* TOTPFetcher.swift */; }; 66E8ECA72381CB5100945BEA /* TwoFactorTokenGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E8ECA62381CB5100945BEA /* TwoFactorTokenGenerator.swift */; }; 66E8ECA92381CD2F00945BEA /* TOTPNetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E8ECA82381CD2F00945BEA /* TOTPNetworkManager.swift */; }; + 6C11C08026F842CF00407C04 /* HomeUpdateCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C11C07F26F842CF00407C04 /* HomeUpdateCellItem.swift */; }; 6C23AF9526E57903002F60F0 /* (null) in Sources */ = {isa = PBXBuildFile; }; 6C369A1526E39BC100721CA1 /* (null) in Sources */ = {isa = PBXBuildFile; }; 6C4CC1FA26E6B1720000B4A8 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 6C4CC1F926E6B1720000B4A8 /* SwiftyJSON */; }; @@ -484,6 +485,7 @@ 66E8ECA42381CAA800945BEA /* TOTPFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOTPFetcher.swift; sourceTree = ""; }; 66E8ECA62381CB5100945BEA /* TwoFactorTokenGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoFactorTokenGenerator.swift; sourceTree = ""; }; 66E8ECA82381CD2F00945BEA /* TOTPNetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOTPNetworkManager.swift; sourceTree = ""; }; + 6C11C07F26F842CF00407C04 /* HomeUpdateCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeUpdateCellItem.swift; sourceTree = ""; }; 6C6035ED26E722890025FBC7 /* DiningVenueDetailLocationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiningVenueDetailLocationView.swift; sourceTree = ""; }; 6C6035EE26E722890025FBC7 /* DiningVenueDetailMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiningVenueDetailMenuView.swift; sourceTree = ""; }; 6C6035EF26E722890025FBC7 /* MenuDisclosureGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuDisclosureGroup.swift; sourceTree = ""; }; @@ -762,6 +764,7 @@ F2568A792413637D00561295 /* HomeCellSafeArea.swift */, 211DA1AA20490E4D0065BC2C /* HomeCellConformable.swift */, 210AC14420684F9B0050D837 /* HomeCellProtocols.swift */, + 6C11C07E26F841CB00407C04 /* Update */, B98CA5F723E73631004B3218 /* Group Invites */, 212B835A222A37BD00F835D6 /* Feature */, 212B8353222A319200F835D6 /* Post */, @@ -1224,6 +1227,14 @@ path = Auth; sourceTree = ""; }; + 6C11C07E26F841CB00407C04 /* Update */ = { + isa = PBXGroup; + children = ( + 6C11C07F26F842CF00407C04 /* HomeUpdateCellItem.swift */, + ); + path = Update; + sourceTree = ""; + }; 6C6035EC26E722890025FBC7 /* Detail View */ = { isa = PBXGroup; children = ( @@ -2184,6 +2195,7 @@ F23CC0C1235E3F8A0007317A /* DiningVenue+Extensions.swift in Sources */, 217A7834204D2DF5004F1227 /* HomeDiningCellItem.swift in Sources */, C11DFA31219F90E5000FC573 /* CalendarEvent.swift in Sources */, + 6C11C08026F842CF00407C04 /* HomeUpdateCellItem.swift in Sources */, F2E3FE2A259722C500518961 /* SearchResultsPopover.swift in Sources */, 97E79E072100DA1200D3D606 /* BuildingImageCell.swift in Sources */, 217A783F204D97F8004F1227 /* ModularTableViewItem.swift in Sources */, diff --git a/PennMobile/GSR-Booking/Model/GSRReservation.swift b/PennMobile/GSR-Booking/Model/GSRReservation.swift index 277877e05..3cd4adafd 100644 --- a/PennMobile/GSR-Booking/Model/GSRReservation.swift +++ b/PennMobile/GSR-Booking/Model/GSRReservation.swift @@ -13,7 +13,7 @@ enum GSRService: String { case libcal } -struct GSRReservation: Codable { +struct GSRReservation: Codable, Equatable { let bookingId: String let gsr: GSRLocation let roomId: Int diff --git a/PennMobile/Home/Cells/Calendar/HomeCalendarCell.swift b/PennMobile/Home/Cells/Calendar/HomeCalendarCell.swift index ad279a36c..81c602df4 100644 --- a/PennMobile/Home/Cells/Calendar/HomeCalendarCell.swift +++ b/PennMobile/Home/Cells/Calendar/HomeCalendarCell.swift @@ -23,7 +23,7 @@ final class HomeCalendarCell: UITableViewCell, HomeCellConformable { static func getCellHeight(for item: ModularTableViewItem) -> CGFloat { guard let item = item as? HomeCalendarCellItem else { return 0.0 } // cell height = (venues * venueHeight) + header + footer + cellInset - return (CGFloat(item.events?.count ?? 0) * UniversityNotificationCell.cellHeight) + HomeCellHeader.height + (Padding.pad * 3) + return (CGFloat(item.events.count) * UniversityNotificationCell.cellHeight) + HomeCellHeader.height + (Padding.pad * 3) } var events: [CalendarEvent]? diff --git a/PennMobile/Home/Cells/Calendar/HomeCalendarCellItem.swift b/PennMobile/Home/Cells/Calendar/HomeCalendarCellItem.swift index b485fb854..6af400ffb 100644 --- a/PennMobile/Home/Cells/Calendar/HomeCalendarCellItem.swift +++ b/PennMobile/Home/Cells/Calendar/HomeCalendarCellItem.swift @@ -15,20 +15,29 @@ final class HomeCalendarCellItem: HomeCellItem { return "calendar" } - var events: [CalendarEvent]? + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + CalendarAPI.instance.fetchCalendar({ events in + if let events = events, events.count > 0 { + completion([HomeCalendarCellItem(for: events)]) + } else { + completion([]) + } + }) + } - static func getItem(for json: JSON?) -> HomeCellItem? { - return HomeCalendarCellItem() + init(for events: [CalendarEvent]) { + self.events = events } + var events: [CalendarEvent] + static var associatedCell: ModularTableViewCell.Type { return HomeCalendarCell.self } func equals(item: ModularTableViewItem) -> Bool { guard let item = item as? HomeCalendarCellItem else { return false } - guard let events = events, let itemEvents = item.events else { return false } - return events == itemEvents + return events == item.events } } diff --git a/PennMobile/Home/Cells/Courses/HomeCoursesCellItem.swift b/PennMobile/Home/Cells/Courses/HomeCoursesCellItem.swift index d21bd651b..988a57c1c 100644 --- a/PennMobile/Home/Cells/Courses/HomeCoursesCellItem.swift +++ b/PennMobile/Home/Cells/Courses/HomeCoursesCellItem.swift @@ -10,11 +10,6 @@ import Foundation import SwiftyJSON final class HomeCoursesCellItem: HomeCellItem { - - static var jsonKey: String { - return "courses" - } - let weekday: String var courses: [Course] var isOnHomeScreen: Bool @@ -25,42 +20,34 @@ final class HomeCoursesCellItem: HomeCellItem { self.isOnHomeScreen = isOnHomeScreen } - static func getItem(for json: JSON?) -> HomeCellItem? { - if let json = json, let weekday = json["weekday"].string, let data: Data = try? json["courses"].rawData() { - // Courses provided by server - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase - do { - let courses = try decoder.decode([Course].self, from: data) - return HomeCoursesCellItem(weekday: weekday, courses: courses, isOnHomeScreen: true) - } catch { - return nil - } - } else if let courses = UserDefaults.standard.getCourses() { - // Courses not provided by server. Use courses saved on device. + static var jsonKey: String { + return "courses" + } + + static var associatedCell: ModularTableViewCell.Type { + return HomeCoursesCell.self + } + + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + if let courses = UserDefaults.standard.getCourses() { let coursesToday = courses.taughtToday if coursesToday.hasUpcomingCourse { let weekday = "Today" - return HomeCoursesCellItem(weekday: weekday, courses: Array(coursesToday), isOnHomeScreen: true) + return completion([HomeCoursesCellItem(weekday: weekday, courses: Array(coursesToday), isOnHomeScreen: true)]) } else { let weekday = "Tomorrow" let coursesTomorrow = courses.taughtTomorrow if !coursesTomorrow.isEmpty { - return HomeCoursesCellItem(weekday: weekday, courses: Array(coursesTomorrow), isOnHomeScreen: true) + return completion([HomeCoursesCellItem(weekday: weekday, courses: Array(coursesTomorrow), isOnHomeScreen: true)]) } else { - return nil + return completion([]) } } - } else { - return nil + return completion([]) } } - static var associatedCell: ModularTableViewCell.Type { - return HomeCoursesCell.self - } - func equals(item: ModularTableViewItem) -> Bool { guard let item = item as? HomeCoursesCellItem else { return false } return weekday == item.weekday diff --git a/PennMobile/Home/Cells/Dining/HomeDiningCellItem.swift b/PennMobile/Home/Cells/Dining/HomeDiningCellItem.swift index 230040317..2d79a0a7f 100644 --- a/PennMobile/Home/Cells/Dining/HomeDiningCellItem.swift +++ b/PennMobile/Home/Cells/Dining/HomeDiningCellItem.swift @@ -10,52 +10,32 @@ import Foundation import SwiftyJSON final class HomeDiningCellItem: HomeCellItem { + + static var jsonKey: String { + return "dining" + } + static var associatedCell: ModularTableViewCell.Type { return HomeDiningCell.self } var venues: [DiningVenue] - var venueIds: [Int] - init(venues: [DiningVenue], venueIds: [Int]) { + init(for venues: [DiningVenue]) { self.venues = venues - self.venueIds = venueIds } func equals(item: ModularTableViewItem) -> Bool { guard let item = item as? HomeDiningCellItem else { return false } return venues == item.venues } - - static var jsonKey: String { - return "dining" - } - - static func getItem(for json: JSON?) -> HomeCellItem? { - guard let json = json else { return nil } - return try? HomeDiningCellItem(json: json) - } -} - -// MARK: - JSON Parsing -extension HomeDiningCellItem { - convenience init(json: JSON) throws { - guard let ids = json["venues"].arrayObject as? [Int] else { - throw NetworkingError.jsonError - } - let venues: [DiningVenue] = DiningAPI.instance.getVenues(with: ids) - self.init(venues: venues, venueIds: ids) - } -} -// MARK: - API Fetching -extension HomeDiningCellItem: HomeAPIRequestable { - func fetchData(_ completion: @escaping () -> Void) { + static func getHomeCellItem(_ completion: @escaping((_ items: [HomeCellItem]) -> Void)) { DiningAPI.instance.fetchDiningHours { _ in - if self.venues.isEmpty { - self.venues = DiningAPI.instance.getVenues() - } - completion() + let venues = DiningAPI.instance.getVenues() + let instance = HomeDiningCellItem(for: venues) + + completion([instance]) } } } diff --git a/PennMobile/Home/Cells/Feature/HomeFeatureCellItem.swift b/PennMobile/Home/Cells/Feature/HomeFeatureCellItem.swift index ebdd46f90..8690a817c 100644 --- a/PennMobile/Home/Cells/Feature/HomeFeatureCellItem.swift +++ b/PennMobile/Home/Cells/Feature/HomeFeatureCellItem.swift @@ -10,6 +10,9 @@ import Foundation import SwiftyJSON final class HomeFeatureCellItem: HomeCellItem { + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + completion([]) + } static var jsonKey: String { return "feature" diff --git a/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift b/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift index 2d9c05e92..13f1647a0 100644 --- a/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift +++ b/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift @@ -10,8 +10,33 @@ import Foundation import SwiftyJSON final class HomeGSRLocationsCellItem: HomeCellItem { - static var associatedCell: ModularTableViewCell.Type { - return HomeGSRLocationsCell.self + static var jsonKey = "gsr-locations" + static var associatedCell: ModularTableViewCell.Type = HomeGSRLocationsCell.self + + static func getHomeCellItem(_ completion: @escaping ([HomeCellItem]) -> Void) { + OAuth2NetworkManager.instance.getAccessToken { token in + if let token = token { + + let request = URLRequest(url: URL(string: "https://studentlife.pennlabs.org/penndata/gsrs/")!, accessToken: token) + + let task = URLSession.shared.dataTask(with: request) { data, response, error in + + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + + if let data = data, let locations = try? decoder.decode([GSRLocation].self, from: data), locations.count > 0 { + completion([HomeGSRLocationsCellItem(locations: locations)]) + } else { + completion([]) + } + } + + task.resume() + } else { + let locationSlice = GSRLocationModel.shared.getLocations().shuffle().prefix(upTo: 3) + completion([HomeGSRLocationsCellItem(locations: Array(locationSlice))]) + } + } } let locations: [GSRLocation] @@ -24,15 +49,4 @@ final class HomeGSRLocationsCellItem: HomeCellItem { guard let item = item as? HomeGSRLocationsCellItem else { return false } return locations == item.locations } - - static var jsonKey: String { - return "gsr-locations" - } - - static func getItem(for json: JSON?) -> HomeCellItem? { - guard let lids = json?.arrayObject as? [Int] else { return nil } - var locations = GSRLocationModel.shared.getLocations().filter { lids.contains( $0.lid ) } - locations = locations.filter { $0.lid != 1086 || $0.gid == 1889 } - return HomeGSRLocationsCellItem(locations: locations) - } } diff --git a/PennMobile/Home/Cells/GSR Reservations/HomeReservationsCellItem.swift b/PennMobile/Home/Cells/GSR Reservations/HomeReservationsCellItem.swift index 665526743..4a9702b89 100644 --- a/PennMobile/Home/Cells/GSR Reservations/HomeReservationsCellItem.swift +++ b/PennMobile/Home/Cells/GSR Reservations/HomeReservationsCellItem.swift @@ -14,31 +14,33 @@ final class HomeReservationsCellItem: HomeCellItem { return HomeReservationsCell.self } + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + GSRNetworkManager.instance.getReservations { result in + switch result { + case .success(let reservations): + if reservations.count > 0 { + completion([HomeReservationsCellItem(for: reservations)]) + } else { + completion([]) + } + case .failure: + completion([]) + } + } + } + var reservations: [GSRReservation] - init(reservations: [GSRReservation]) { + init(for reservations: [GSRReservation]) { self.reservations = reservations } func equals(item: ModularTableViewItem) -> Bool { guard let item = item as? HomeReservationsCellItem else { return false } - return reservations.count == item.reservations.count + return reservations == item.reservations } static var jsonKey: String { return "reservations" } - - static func getItem(for json: JSON?) -> HomeCellItem? { - guard let json = json else { return nil } - do { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - decoder.keyDecodingStrategy = .convertFromSnakeCase - let reservations = try decoder.decode([GSRReservation].self, from: json.rawData()) - return HomeReservationsCellItem(reservations: reservations) - } catch { - return nil - } - } } diff --git a/PennMobile/Home/Cells/GSR/HomeGSRCellItem.swift b/PennMobile/Home/Cells/GSR/HomeGSRCellItem.swift index 8a653ed8f..6f601b4f2 100644 --- a/PennMobile/Home/Cells/GSR/HomeGSRCellItem.swift +++ b/PennMobile/Home/Cells/GSR/HomeGSRCellItem.swift @@ -10,6 +10,9 @@ import Foundation import SwiftyJSON final class HomeGSRCellItem: HomeCellItem { + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + completion([]) + } static var associatedCell: ModularTableViewCell.Type { return HomeStudyRoomCell.self diff --git a/PennMobile/Home/Cells/Group Invites/HomeGroupInvitesCellItem.swift b/PennMobile/Home/Cells/Group Invites/HomeGroupInvitesCellItem.swift index 1bfe0732c..e584065dc 100644 --- a/PennMobile/Home/Cells/Group Invites/HomeGroupInvitesCellItem.swift +++ b/PennMobile/Home/Cells/Group Invites/HomeGroupInvitesCellItem.swift @@ -10,29 +10,21 @@ import Foundation import SwiftyJSON final class HomeGroupInvitesCellItem: HomeCellItem { - static var associatedCell: ModularTableViewCell.Type { - return HomeGroupInvitesCell.self - } + static var jsonKey = "invites" + static var associatedCell: ModularTableViewCell.Type = HomeGroupInvitesCell.self var invites: GSRGroupInvites - init(invites: GSRGroupInvites) { + init(for invites: GSRGroupInvites) { self.invites = invites } + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + completion([]) + } + func equals(item: ModularTableViewItem) -> Bool { guard let item = item as? HomeGroupInvitesCellItem else {return false} return invites.count == item.invites.count } - - static var jsonKey: String { - return "invites" - } - - static func getItem(for json: JSON?) -> HomeCellItem? { - // comment out following line once server is updated to show cell - guard let json = json else { return nil } - guard let invites = try? JSONDecoder().decode(GSRGroupInvites.self, from: json.rawData()) else { return nil } - return HomeGroupInvitesCellItem(invites: invites) - } } diff --git a/PennMobile/Home/Cells/Laundry/HomeLaundryCellItem.swift b/PennMobile/Home/Cells/Laundry/HomeLaundryCellItem.swift index a8931670c..701e16078 100644 --- a/PennMobile/Home/Cells/Laundry/HomeLaundryCellItem.swift +++ b/PennMobile/Home/Cells/Laundry/HomeLaundryCellItem.swift @@ -14,6 +14,10 @@ final class HomeLaundryCellItem: HomeCellItem { return HomeLaundryCell.self } + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + completion([]) + } + var room: LaundryRoom init(room: LaundryRoom) { diff --git a/PennMobile/Home/Cells/News/HomeNewsCell.swift b/PennMobile/Home/Cells/News/HomeNewsCell.swift index 4fee45165..bd4b4155a 100644 --- a/PennMobile/Home/Cells/News/HomeNewsCell.swift +++ b/PennMobile/Home/Cells/News/HomeNewsCell.swift @@ -92,7 +92,7 @@ final class HomeNewsCell: UITableViewCell, HomeCellConformable { extension HomeNewsCell { fileprivate func setupCell(with item: HomeNewsCellItem) { self.article = item.article - self.articleImageView.image = item.image + self.articleImageView.kf.setImage(with: URL(string: item.article.imageurl)) self.sourceLabel.text = article.source self.titleLabel.text = article.title self.subtitleLabel?.text = article.subtitle @@ -111,7 +111,7 @@ extension HomeNewsCell { @objc fileprivate func handleTapped(_ sender: Any) { guard let delegate = delegate as? URLSelectable else { return } FirebaseAnalyticsManager.shared.trackEvent(action: "News Cell Pressed", result: article.title, content: "") - delegate.handleUrlPressed(urlStr: article.articleUrl, title: article.source, item: self.item, shouldLog: true) + delegate.handleUrlPressed(urlStr: article.link, title: article.source, item: self.item, shouldLog: true) } } diff --git a/PennMobile/Home/Cells/News/HomeNewsCellItem.swift b/PennMobile/Home/Cells/News/HomeNewsCellItem.swift index 3159ee249..3692bf27e 100644 --- a/PennMobile/Home/Cells/News/HomeNewsCellItem.swift +++ b/PennMobile/Home/Cells/News/HomeNewsCellItem.swift @@ -7,59 +7,41 @@ // import Foundation -import SwiftyJSON final class HomeNewsCellItem: HomeCellItem { - - static var jsonKey: String { - return "news" - } + static var jsonKey = "news" + static var associatedCell: ModularTableViewCell.Type = HomeNewsCell.self let article: NewsArticle - var image: UIImage? var showSubtitle = false - init(article: NewsArticle) { + init(for article: NewsArticle) { self.article = article } - static func getItem(for json: JSON?) -> HomeCellItem? { - guard let json = json else { return nil } - return try? HomeNewsCellItem(json: json) - } - - static var associatedCell: ModularTableViewCell.Type { - return HomeNewsCell.self + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + let task = URLSession.shared.dataTask(with: URL(string: "https://studentlife.pennlabs.org/penndata/news/")!) { data, response, error in + guard let data = data else { completion([]); return } + + if let article = try? JSONDecoder().decode(NewsArticle.self, from: data) { + completion([HomeNewsCellItem(for: article)]) + } else { + completion([]) + } + } + + task.resume() } - + func equals(item: ModularTableViewItem) -> Bool { guard let item = item as? HomeNewsCellItem else { return false } return article.title == item.article.title } } -// MARK: - HomeAPIRequestable -extension HomeNewsCellItem: HomeAPIRequestable { - func fetchData(_ completion: @escaping () -> Void) { - ImageNetworkingManager.instance.downloadImage(imageUrl: article.imageUrl) { (image) in - self.image = image - completion() - } - } -} - -// MARK: - JSON Parsing -extension HomeNewsCellItem { - convenience init(json: JSON) throws { - let article = try NewsArticle(json: json) - self.init(article: article) - self.showSubtitle = json["show_subtitle"].boolValue - } -} - // MARK: - Logging ID extension HomeNewsCellItem: LoggingIdentifiable { var id: String { - return article.articleUrl + return article.link } } diff --git a/PennMobile/Home/Cells/News/NewsArticle.swift b/PennMobile/Home/Cells/News/NewsArticle.swift index e139e74b2..b0b367fe0 100644 --- a/PennMobile/Home/Cells/News/NewsArticle.swift +++ b/PennMobile/Home/Cells/News/NewsArticle.swift @@ -7,38 +7,12 @@ // import Foundation -import SwiftyJSON -class NewsArticle { +struct NewsArticle: Codable { let source: String + let link: String let title: String let subtitle: String let timestamp: String - let imageUrl: String - let articleUrl: String - - init(source: String, title: String, subtitle: String, timestamp: String, imageUrl: String, articleUrl: String) { - self.source = source - self.title = title - self.subtitle = subtitle - self.timestamp = timestamp - self.imageUrl = imageUrl - self.articleUrl = articleUrl - } -} - -// MARK: - JSON Parsing -extension NewsArticle { - convenience init(json: JSON) throws { - guard let source = json["source"].string, - let title = json["title"].string, - let subtitle = json["subtitle"].string, - let timestamp = json["timestamp"].string, - let imageUrl = json["image_url"].string, - let articleUrl = json["article_url"].string else { - throw NetworkingError.jsonError - } - - self.init(source: source, title: title, subtitle: subtitle, timestamp: timestamp, imageUrl: imageUrl, articleUrl: articleUrl) - } + let imageurl: String } diff --git a/PennMobile/Home/Cells/Post/HomePostCellItem.swift b/PennMobile/Home/Cells/Post/HomePostCellItem.swift index a4c6cd52d..4c69e4f94 100644 --- a/PennMobile/Home/Cells/Post/HomePostCellItem.swift +++ b/PennMobile/Home/Cells/Post/HomePostCellItem.swift @@ -10,6 +10,9 @@ import Foundation import SwiftyJSON final class HomePostCellItem: HomeCellItem { + static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { + completion([]) + } static var jsonKey: String { return "post" @@ -61,10 +64,3 @@ extension HomePostCellItem: LoggingIdentifiable { return String(post.id) } } - -// MARK: - Testable -extension HomePostCellItem: FeedTestable { - var isTest: Bool { - return post.isTest - } -} diff --git a/PennMobile/Home/Controllers/HomeViewController.swift b/PennMobile/Home/Controllers/HomeViewController.swift index 3c27a5745..44cd50c4d 100644 --- a/PennMobile/Home/Controllers/HomeViewController.swift +++ b/PennMobile/Home/Controllers/HomeViewController.swift @@ -174,32 +174,16 @@ extension HomeViewController { // MARK: - Networking extension HomeViewController { - func fetchViewModel(_ secondAttempt: Bool = false, _ completion: @escaping () -> Void) { - HomeAPIService.instance.fetchModel { (model, error) in - DispatchQueue.main.async { - if error != nil { - let navigationVC = self.navigationController as? HomeNavigationController - - if !secondAttempt { - self.fetchViewModel(true, completion) - } else { - navigationVC?.addStatusBar(text: .apiError) - completion() - } - return - } - - guard let model = model else { return } - self.setModel(model) - UIView.transition(with: self.tableView, - duration: 0.35, - options: .transitionCrossDissolve, - animations: { self.tableView.reloadData() }) - self.fetchAllCellData { - // Do anything here that needs to be done after all the cells are loaded - } - completion() - } + func fetchViewModel(_ completion: @escaping () -> Void) { + HomeAPIService.instance.fetchModel { model in + self.setModel(model) + + UIView.transition(with: self.tableView, + duration: 0.35, + options: .transitionCrossDissolve, + animations: { self.tableView.reloadData() }) + + completion() } } diff --git a/PennMobile/Home/Model/HomeCellItem.swift b/PennMobile/Home/Model/HomeCellItem.swift index b8d5512eb..4cc2d0395 100644 --- a/PennMobile/Home/Model/HomeCellItem.swift +++ b/PennMobile/Home/Model/HomeCellItem.swift @@ -11,13 +11,9 @@ import SwiftyJSON protocol HomeCellItem: ModularTableViewItem { static var jsonKey: String { get } - static func getItem(for json: JSON?) -> HomeCellItem? + static func getHomeCellItem(_ completion: @escaping((_ item: [HomeCellItem]) -> Void)) } protocol LoggingIdentifiable where Self: HomeCellItem { var id: String { get } } - -protocol FeedTestable where Self: HomeCellItem { - var isTest: Bool { get } -} diff --git a/PennMobile/Home/Model/HomeItemTypes.swift b/PennMobile/Home/Model/HomeItemTypes.swift index e6767c704..13614a79d 100644 --- a/PennMobile/Home/Model/HomeItemTypes.swift +++ b/PennMobile/Home/Model/HomeItemTypes.swift @@ -24,6 +24,7 @@ final class HomeItemTypes: ModularTableViewItemTypes { let courses: HomeCellItem.Type = HomeCoursesCellItem.self let gsrLocations: HomeCellItem.Type = HomeGSRLocationsCellItem.self let invites: HomeCellItem.Type = HomeGroupInvitesCellItem.self +// let update: HomeCellItem.Type = HomeUpdateCellItem.self } // MARK: - JSON Parsing diff --git a/PennMobile/Home/Model/HomeTableViewModel.swift b/PennMobile/Home/Model/HomeTableViewModel.swift index 4dc842c23..247a9b482 100644 --- a/PennMobile/Home/Model/HomeTableViewModel.swift +++ b/PennMobile/Home/Model/HomeTableViewModel.swift @@ -41,13 +41,7 @@ extension HomeTableViewModel { if let identifiableItem = item as? LoggingIdentifiable { id = identifiableItem.id } - - let cellTypeStr: String - if let testableItem = item as? FeedTestable, testableItem.isTest { - cellTypeStr = "test-\(cellType.jsonKey)" - } else { - cellTypeStr = cellType.jsonKey - } - FeedAnalyticsManager.shared.track(cellType: cellTypeStr, index: indexPath.row, id: id, batchSize: items.count) + + FeedAnalyticsManager.shared.track(cellType: cellType.jsonKey, index: indexPath.row, id: id, batchSize: items.count) } } diff --git a/PennMobile/Home/Networking/HomeAPIService.swift b/PennMobile/Home/Networking/HomeAPIService.swift index 2d2b449bd..862fb0ac4 100644 --- a/PennMobile/Home/Networking/HomeAPIService.swift +++ b/PennMobile/Home/Networking/HomeAPIService.swift @@ -9,85 +9,29 @@ import Foundation import SwiftyJSON -final class HomeAPIService: Requestable { +final class HomeAPIService { static let instance = HomeAPIService() - private init() {} - func fetchModel(_ completion: @escaping (_ model: HomeTableViewModel?, _ error: NetworkingError?) -> Void) { - let version = UserDefaults.standard.getAppVersion() - var url = "https://api.pennlabs.org/homepage?version=\(version)" - if let sessionID = GSRUser.getSessionID() { - url = "\(url)&sessionid=\(sessionID)" - } - if let courses = UserDefaults.standard.getCourses(), !courses.enrolledIn.isEmpty { - if courses.taughtToday.hasUpcomingCourse { - url = "\(url)&hasCourses=today" - } else if !courses.taughtTomorrow.isEmpty { - url = "\(url)&hasCourses=tomorrow" - } - } - - url = "\(url)&groupsEnabled=\(UserDefaults.standard.gsrGroupsEnabled())" + func fetchModel(_ completion: @escaping ((HomeTableViewModel) -> Void)) { + let group = DispatchGroup() - OAuth2NetworkManager.instance.getAccessToken { (token) in - // Make request without access token if one does not exist - let url = URL(string: url)! - var request = token != nil ? URLRequest(url: url, accessToken: token!) : URLRequest(url: url) - - // Add device ID to request to access data associated associated with device id (ex: favorite dining halls) - let deviceID = getDeviceID() - request.setValue(deviceID, forHTTPHeaderField: "X-Device-ID") - - let task = URLSession.shared.dataTask(with: request) { (data, response, error) in - if let error = error, (error as NSError).code == -1009 { - completion(nil, NetworkingError.noInternet) - return - } - if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { - completion(nil, NetworkingError.serverError) - return - } - var model: HomeTableViewModel? = HomeTableViewModel() - var error: NetworkingError? = NetworkingError.jsonError - if let data = data { - let json = JSON(data) - model = try? HomeTableViewModel(json: json) - if model != nil { - error = nil - } + let model = HomeTableViewModel() + + // Fetch HomeCellItem for all HomeItemTypes + for item in HomeItemTypes.instance.getAllTypes() { + group.enter() + item.getHomeCellItem { item in + item.forEach { i in + model.items.append(i) } - completion(model, error) - } - task.resume() - } - } -} - -extension HomeTableViewModel { - convenience init(json: JSON) throws { - self.init() - - guard let cellsJSON = json["cells"].array else { - throw NetworkingError.jsonError - } - - self.items = [HomeCellItem]() - - // Initialize default items for development - // Note: this should be empty in production - for ItemType in HomeItemTypes.instance.getDefaultItems() { - if let item = ItemType.getItem(for: nil) { - items.append(item) + + group.leave() } } - - // Initialize items from JSON - for json in cellsJSON { - let type = json["type"].stringValue - let infoJSON = json["info"] - if let ItemType = HomeItemTypes.instance.getItemType(for: type), let item = ItemType.getItem(for: infoJSON) { - items.append(item) - } + + // Handle completion of model after it is done + group.notify(queue: .main) { + completion(model) } } } From 56d86c17205628e27c7cb6d8281b866f5a028bae Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Sun, 14 Nov 2021 12:32:40 -0500 Subject: [PATCH 05/21] merge build failure --- .../HomeGSRLocationsCellItem.swift | 4 ---- .../Cells/Update/HomeUpdateCellItem.swift | 23 +++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 PennMobile/Home/Cells/Update/HomeUpdateCellItem.swift diff --git a/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift b/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift index 4ac106770..13f1647a0 100644 --- a/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift +++ b/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift @@ -39,10 +39,6 @@ final class HomeGSRLocationsCellItem: HomeCellItem { } } - static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { - completion([]) - } - let locations: [GSRLocation] init(locations: [GSRLocation]) { diff --git a/PennMobile/Home/Cells/Update/HomeUpdateCellItem.swift b/PennMobile/Home/Cells/Update/HomeUpdateCellItem.swift new file mode 100644 index 000000000..7ca027dfc --- /dev/null +++ b/PennMobile/Home/Cells/Update/HomeUpdateCellItem.swift @@ -0,0 +1,23 @@ +// +// HomeUpdateCellItem.swift +// PennMobile +// +// Created by CHOI Jongmin on 20/9/2021. +// Copyright © 2021 PennLabs. All rights reserved. +// + +import Foundation +// +//class HomeUpdateCellItem: HomeCellItem { +// static var jsonKey = "update" +// +// static var associatedCell: ModularTableViewCell.Type +// +// static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { +// completion([]) +// } +// +// func equals(item: ModularTableViewItem) -> Bool { +// return true +// } +//} From 8ea8af5d9aebf92498c72454acef55908e0058e4 Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Fri, 21 Jan 2022 20:43:03 -0500 Subject: [PATCH 06/21] Updateless migration complete --- PennMobile.xcodeproj/project.pbxproj | 8 --- PennMobile/Auth/OAuth2NetworkManager.swift | 1 - .../Dining/Networking + Cache/DiningAPI.swift | 45 ++---------- .../Networking/GSRNetworkManager.swift | 40 ----------- .../UserDBManager.swift | 50 ++++++++++--- .../Home/Cells/Calendar/CalendarAPI.swift | 36 ++-------- .../Home/Cells/Calendar/CalendarEvent.swift | 35 ++-------- .../Cells/Calendar/HomeCalendarCellItem.swift | 13 ---- .../Calendar/UniversityNotificationCell.swift | 4 +- .../Cells/Dining/HomeDiningCellItem.swift | 15 ++-- .../Cells/Feature/HomeFeatureCellItem.swift | 23 ------ .../HomeGSRLocationsCellItem.swift | 2 +- .../Home/Cells/GSR/HomeGSRCellItem.swift | 70 ------------------- .../Cells/Laundry/HomeLaundryCellItem.swift | 28 ++++---- .../Home/Cells/News/HomeNewsCellItem.swift | 2 +- .../Home/Cells/Post/HomePostCellItem.swift | 38 ++++------ .../Home/Controllers/HomeViewController.swift | 36 ++-------- PennMobile/Home/Model/HomeCellItem.swift | 9 ++- PennMobile/Home/Model/HomeItemTypes.swift | 28 +------- .../Home/Networking/HomeAPIService.swift | 19 ++++- .../LaundryTableViewController.swift | 2 + .../Networking/LaundryAPIService.swift | 15 ++-- .../Login/CampusExpressNetworkManager.swift | 18 +++++ .../Setup + Navigation/AppDelegate.swift | 20 +++--- .../RootViewController.swift | 1 + .../SplashViewController.swift | 18 ++++- Podfile.lock | 2 +- 27 files changed, 191 insertions(+), 387 deletions(-) diff --git a/PennMobile.xcodeproj/project.pbxproj b/PennMobile.xcodeproj/project.pbxproj index fd55d7455..1740c5d0c 100644 --- a/PennMobile.xcodeproj/project.pbxproj +++ b/PennMobile.xcodeproj/project.pbxproj @@ -36,7 +36,6 @@ 21508168220D24A1002F7EA1 /* HomeNewsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21508167220D24A1002F7EA1 /* HomeNewsCell.swift */; }; 2150816A220D24C4002F7EA1 /* NewsArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21508169220D24C4002F7EA1 /* NewsArticle.swift */; }; 215C605A205C837400FE271A /* FlingNetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215C6059205C837400FE271A /* FlingNetworkManager.swift */; }; - 215C605D205C88EA00FE271A /* ImageNetworkingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215C605C205C88EA00FE271A /* ImageNetworkingManager.swift */; }; 21640D3E200ED954002F33CA /* LaundryMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21640D3D200ED954002F33CA /* LaundryMachine.swift */; }; 21640D40200EF881002F33CA /* LaundryCell + Graph.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21640D3F200EF881002F33CA /* LaundryCell + Graph.swift */; }; 21640D4520103EB8002F33CA /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21640D4420103EB8002F33CA /* HomeViewController.swift */; }; @@ -66,7 +65,6 @@ 217A783F204D97F8004F1227 /* ModularTableViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217A783E204D97F8004F1227 /* ModularTableViewItem.swift */; }; 217A7841204D9802004F1227 /* ModularTableViewItemTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217A7840204D9802004F1227 /* ModularTableViewItemTypes.swift */; }; 217A7843204D995C004F1227 /* ModularTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217A7842204D995C004F1227 /* ModularTableViewCell.swift */; }; - 217A7845204DB800004F1227 /* HomeAsynchronousFetching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217A7844204DB800004F1227 /* HomeAsynchronousFetching.swift */; }; 217C6DD920139AC500F07C3D /* AsynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217C6DD820139AC500F07C3D /* AsynchronousOperation.swift */; }; 2180D2302013F3B4008C94CF /* NotificationRequestable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2180D22F2013F3B4008C94CF /* NotificationRequestable.swift */; }; 2180D2352013F65C008C94CF /* HomeAPIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2180D2342013F65C008C94CF /* HomeAPIService.swift */; }; @@ -370,7 +368,6 @@ 21508167220D24A1002F7EA1 /* HomeNewsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeNewsCell.swift; sourceTree = ""; }; 21508169220D24C4002F7EA1 /* NewsArticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsArticle.swift; sourceTree = ""; }; 215C6059205C837400FE271A /* FlingNetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlingNetworkManager.swift; sourceTree = ""; }; - 215C605C205C88EA00FE271A /* ImageNetworkingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageNetworkingManager.swift; sourceTree = ""; }; 21640D3D200ED954002F33CA /* LaundryMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaundryMachine.swift; sourceTree = ""; }; 21640D3F200EF881002F33CA /* LaundryCell + Graph.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LaundryCell + Graph.swift"; sourceTree = ""; }; 21640D4420103EB8002F33CA /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; @@ -405,7 +402,6 @@ 217A783E204D97F8004F1227 /* ModularTableViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModularTableViewItem.swift; sourceTree = ""; }; 217A7840204D9802004F1227 /* ModularTableViewItemTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModularTableViewItemTypes.swift; sourceTree = ""; }; 217A7842204D995C004F1227 /* ModularTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModularTableViewCell.swift; sourceTree = ""; }; - 217A7844204DB800004F1227 /* HomeAsynchronousFetching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeAsynchronousFetching.swift; sourceTree = ""; }; 217C6DD820139AC500F07C3D /* AsynchronousOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsynchronousOperation.swift; sourceTree = ""; }; 2180D22F2013F3B4008C94CF /* NotificationRequestable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestable.swift; sourceTree = ""; }; 2180D2342013F65C008C94CF /* HomeAPIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeAPIService.swift; sourceTree = ""; }; @@ -1006,7 +1002,6 @@ isa = PBXGroup; children = ( 2180D2342013F65C008C94CF /* HomeAPIService.swift */, - 217A7844204DB800004F1227 /* HomeAsynchronousFetching.swift */, ); path = Networking; sourceTree = ""; @@ -1172,7 +1167,6 @@ children = ( 217C6DD820139AC500F07C3D /* AsynchronousOperation.swift */, 216640CA1EBADCA400746B8E /* FirebaseAnalyticsManager.swift */, - 215C605C205C88EA00FE271A /* ImageNetworkingManager.swift */, 21FBC23F228514ED00B432D8 /* FeedAnalyticsEngine.swift */, 21EB4D2F203D330C0029A460 /* UserDBManager.swift */, 214C23B11F2FEE150004487C /* Networking.swift */, @@ -2141,7 +2135,6 @@ 2189C0A92027CE4B00771C1F /* RangeSlider.swift in Sources */, 6CAE4391253370E600BD0200 /* DiningVenueRow.swift in Sources */, F2562A262551F6D40021C92F /* CourseAlertSettingsController.swift in Sources */, - 217A7845204DB800004F1227 /* HomeAsynchronousFetching.swift in Sources */, C11DFA2F219F8A65000FC573 /* CalendarAPI.swift in Sources */, F2562A2B255830AB0021C92F /* CourseAlertSettingsCell.swift in Sources */, 219F01F91FB8AC22006BBC4E /* RoomSelectionView.swift in Sources */, @@ -2180,7 +2173,6 @@ F2C7E5F5259EE1CA0043A98A /* CourseAlertSettings.swift in Sources */, 211DA1AF204921030065BC2C /* LaundryMachinesView.swift in Sources */, EFE2D6F2239B11050020F6BF /* GroupMemberCell.swift in Sources */, - 215C605D205C88EA00FE271A /* ImageNetworkingManager.swift in Sources */, 2189C0872027CDD700771C1F /* GSRNetworkManager.swift in Sources */, 2138D55522598AA800D67CA2 /* GSRTabController.swift in Sources */, 211DFE541F36A02C00CB73E4 /* DiningAPI.swift in Sources */, diff --git a/PennMobile/Auth/OAuth2NetworkManager.swift b/PennMobile/Auth/OAuth2NetworkManager.swift index 4883111a8..9cc261906 100644 --- a/PennMobile/Auth/OAuth2NetworkManager.swift +++ b/PennMobile/Auth/OAuth2NetworkManager.swift @@ -90,7 +90,6 @@ extension OAuth2NetworkManager { // dev token that expires in a year // use until auth is back up if let accessToken = self.currentAccessToken, Date() < accessToken.expiration { - print(accessToken.value) callback(accessToken) } else { self.currentAccessToken = nil diff --git a/PennMobile/Dining/Networking + Cache/DiningAPI.swift b/PennMobile/Dining/Networking + Cache/DiningAPI.swift index a9c03ce04..5a90d1365 100644 --- a/PennMobile/Dining/Networking + Cache/DiningAPI.swift +++ b/PennMobile/Dining/Networking + Cache/DiningAPI.swift @@ -13,11 +13,10 @@ class DiningAPI: Requestable { static let instance = DiningAPI() - let diningUrl = "https://api.pennlabs.org/dining/venues" - let diningMenuUrl = "https://api.pennlabs.org/dining/daily_menu/" - let diningPrefs = "https://api.pennlabs.org/dining/preferences" - let diningBalanceUrl = "https://api.pennlabs.org/dining/balance" - let diningInsightsUrl = "https://studentlife.pennlabs.org/dining/" + let diningUrl = "https://pennmobile.org/api/dining/venues/" + let diningMenuUrl = "https://pennmobile.org/api/dining/daily_menu/" + // TODO: Broken API, need to fetch locally + let diningInsightsUrl = "https://pennmobile.org/api/dining/" func fetchDiningHours(_ completion: @escaping (_ result: Result) -> Void) { getRequestData(url: diningUrl) { (data, error, statusCode) in @@ -30,7 +29,7 @@ class DiningAPI: Requestable { } guard let data = data else { return completion(.failure(.other)) } - + if let diningAPIResponse = try? JSONDecoder().decode(DiningAPIResponse.self, from: data) { self.saveToCache(diningAPIResponse.document.venues) return completion(.success(diningAPIResponse)) @@ -64,7 +63,6 @@ class DiningAPI: Requestable { func fetchDiningInsights(_ completion: @escaping (_ result: Result) -> Void ) { OAuth2NetworkManager.instance.getAccessToken { (token) in - print("token:" + token!.value) guard let token = token else { // TODO: - Add network error handling for OAuth2 completion(.failure(.noInternet)) @@ -97,7 +95,6 @@ class DiningAPI: Requestable { } task.resume() } - } func fetchDetailPageHTML(for venue: DiningVenue, _ completion: @escaping (_ html: String?) -> Void) { @@ -112,36 +109,8 @@ class DiningAPI: Requestable { // MARK: - Dining Balance API extension DiningAPI { func fetchDiningBalance(_ completion: @escaping (_ diningBalance: DiningBalance?) -> Void) { - OAuth2NetworkManager.instance.getAccessToken { (token) in - guard let token = token else { - completion(nil) - return - } - - let url = URL(string: self.diningBalanceUrl)! - let request = URLRequest(url: url, accessToken: token) - - let task = URLSession.shared.dataTask(with: request) { (data, response, error) in - if let httpResponse = response as? HTTPURLResponse, let data = data, httpResponse.statusCode == 200 { - let json = JSON(data) - let balance = json["balance"] - if let diningDollars = balance["dining_dollars"].float, - let swipes = balance["swipes"].int, - let guestSwipes = balance["guest_swipes"].int, - let timestamp = balance["timestamp"].string { - - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" - if let lastUpdated = formatter.date(from: timestamp){ - let balance = DiningBalance(diningDollars: diningDollars, visits: swipes, guestVisits: guestSwipes, lastUpdated: lastUpdated) - completion(balance) - return - } - } - } - completion(nil) - } - task.resume() + CampusExpressNetworkManager.instance.getDiningBalanceHTML { htmlStr, error in + print(htmlStr) } } } diff --git a/PennMobile/GSR-Booking/Networking/GSRNetworkManager.swift b/PennMobile/GSR-Booking/Networking/GSRNetworkManager.swift index 8248ddd52..5f9e0177e 100644 --- a/PennMobile/GSR-Booking/Networking/GSRNetworkManager.swift +++ b/PennMobile/GSR-Booking/Networking/GSRNetworkManager.swift @@ -48,7 +48,6 @@ class GSRNetworkManager: NSObject, Requestable { var url = URL(string: "\(self.availUrl)")! url.appendPathComponent(lid) url.appendPathComponent("\(gid)") -// print(url) if let startDate = startDate { url.appendQueryItem(name: "start", value: startDate) @@ -189,43 +188,4 @@ extension GSRNetworkManager { task.resume() } } - - func deleteReservation(bookingID: String, sessionID: String?, callback: @escaping (_ success: Bool, _ errorMsg: String?) -> Void) { - let url = URL(string: cancelURL)! - var request = URLRequest(url: url) - request.httpMethod = "POST" - let deviceID = getDeviceID() - request.setValue(deviceID, forHTTPHeaderField: "X-Device-ID") - - var params = ["booking_id": bookingID] - - if let sessionID = sessionID { - params["sessionid"] = sessionID - } - - request.httpBody = params.stringFromHttpParameters().data(using: String.Encoding.utf8) - let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) in - - if error != nil { - callback(false, "Unable to connect to the Internet.") - } else if let httpResponse = response as? HTTPURLResponse { - if httpResponse.statusCode == 200 { - if let data = data, let _ = NSString(data: data, encoding: String.Encoding.utf8.rawValue) { - let json = JSON(data) - if let result = json["result"].array?.first { - let success = result["cancelled"].boolValue - let errorMsg = result["error"].string - callback(success, errorMsg) - return - } else if let errorMsg = json["error"].string { - callback(false, errorMsg) - return - } - } - } - callback(false, "Something went wrong. Please try again.") - } - }) - task.resume() - } } diff --git a/PennMobile/General/Networking + Analytics/UserDBManager.swift b/PennMobile/General/Networking + Analytics/UserDBManager.swift index d2a4b31d2..46236a45e 100644 --- a/PennMobile/General/Networking + Analytics/UserDBManager.swift +++ b/PennMobile/General/Networking + Analytics/UserDBManager.swift @@ -6,7 +6,7 @@ // Copyright © 2018 PennLabs. All rights reserved. // -import Foundation +import UIKit import SwiftyJSON func getDeviceID() -> String { @@ -71,18 +71,46 @@ class UserDBManager: NSObject, Requestable, KeychainAccessible, SHA256Hashable { // MARK: - Dining extension UserDBManager { + func fetchDiningPreferences(_ completion: @escaping(_ result: Result<[DiningVenue], NetworkingError>) -> Void) { + OAuth2NetworkManager.instance.getAccessToken { (token) in + guard let token = token else { + // TODO: - Add network error handling for OAuth2 + completion(.failure(.authenticationError)) + return + } + + let url = URL(string: "https://pennmobile.org/api/dining/preferences/")! + let request = URLRequest(url: url, accessToken: token) + + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + guard let data = data else { + if let error = error as? NetworkingError { + completion(.failure(error)) + } else { + completion(.failure(.other)) + } + return + } + + let diningVenueIds = JSON(data)["preferences"].arrayValue.map({ $0["venue_id"].int! }) + let diningVenues = DiningAPI.instance.getVenues(with: diningVenueIds) + completion(.success(diningVenues)) + + } + + task.resume() + } + } + func saveDiningPreference(for venueIds: [Int]) { - let url = "\(baseUrl)/dining/preferences" - let params = ["venues": venueIds] + let url = "https://pennmobile.org/api/dining/preferences/" OAuth2NetworkManager.instance.getAccessToken { (token) in let url = URL(string: url)! var request = token != nil ? URLRequest(url: url, accessToken: token!) : URLRequest(url: url) request.httpMethod = "POST" - request.httpBody = String.getPostString(params: params).data(using: .utf8) - - let deviceID = getDeviceID() - request.setValue(deviceID, forHTTPHeaderField: "X-Device-ID") + request.httpBody = try? JSON(["venues": venueIds]).rawData() + request.addValue("application/json", forHTTPHeaderField: "Content-Type") let task = URLSession.shared.dataTask(with: request) task.resume() @@ -98,14 +126,16 @@ extension UserDBManager { } func saveLaundryPreferences(for ids: [Int]) { - let url = "\(baseUrl)/laundry/preferences" + let url = "https://pennmobile.org/api/laundry/preferences/" let params = ["rooms": ids] OAuth2NetworkManager.instance.getAccessToken { (token) in let url = URL(string: url)! var request = token != nil ? URLRequest(url: url, accessToken: token!) : URLRequest(url: url) request.httpMethod = "POST" - request.httpBody = String.getPostString(params: params).data(using: .utf8) + + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = try? JSON(params).rawData() let deviceID = getDeviceID() request.setValue(deviceID, forHTTPHeaderField: "X-Device-ID") @@ -116,7 +146,7 @@ extension UserDBManager { } func getLaundryPreferences(_ callback: @escaping (_ rooms: [Int]?) -> Void) { - let url = "\(baseUrl)/laundry/preferences" + let url = "https://pennmobile.org/api/laundry/preferences/" OAuth2NetworkManager.instance.getAccessToken { (token) in let url = URL(string: url)! var request = token != nil ? URLRequest(url: url, accessToken: token!) : URLRequest(url: url) diff --git a/PennMobile/Home/Cells/Calendar/CalendarAPI.swift b/PennMobile/Home/Cells/Calendar/CalendarAPI.swift index 109b45ba5..ca5f027f7 100644 --- a/PennMobile/Home/Cells/Calendar/CalendarAPI.swift +++ b/PennMobile/Home/Cells/Calendar/CalendarAPI.swift @@ -14,42 +14,20 @@ class CalendarAPI: Requestable { static let instance = CalendarAPI() private init() {} - let calendarUrl = "https://api.pennlabs.org/calendar/" + let calendarUrl = "https://pennmobile.org/api/penndata/calendar/" var calendarEvents: [CalendarEvent]? func fetchCalendar(_ completion: @escaping (_ events: [CalendarEvent]?) -> Void) { - - getRequest(url: calendarUrl) { (dict, _, _) in - guard let dict = dict else { + getRequestData(url: calendarUrl) { (data, _, _) in + guard let data = data else { completion(nil) return } - let json = JSON(dict) - if let jsonArray = json["calendar"].array { - var events = [CalendarEvent]() - for json in jsonArray { - let name = json["name"].stringValue - - //set time to 6 am EST - let start = "\(json["start"].stringValue) 6:00 AM" - let end = "\(json["end"].stringValue) 6:00 AM" - - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd h:mm a" - dateFormatter.timeZone = TimeZone(abbreviation: "EST") - - if let startDate = dateFormatter.date(from: start), - let endDate = dateFormatter.date(from: end) { - let event = CalendarEvent(name: name, start: startDate, end: endDate) - events.append(event) - } - - - } - self.calendarEvents = events - completion(events) - } + + let events = try? JSONDecoder().decode([CalendarEvent].self, from: data) + self.calendarEvents = events + completion(events) } } diff --git a/PennMobile/Home/Cells/Calendar/CalendarEvent.swift b/PennMobile/Home/Cells/Calendar/CalendarEvent.swift index 23bf53581..ff49fde10 100644 --- a/PennMobile/Home/Cells/Calendar/CalendarEvent.swift +++ b/PennMobile/Home/Cells/Calendar/CalendarEvent.swift @@ -8,38 +8,11 @@ import Foundation -final class CalendarEvent { - let start: Date - let name: String - let end: Date +struct CalendarEvent: Codable, Equatable { + let event: String + let date: String - init(name: String, start: Date, end: Date) { - self.name = name - self.start = start - self.end = end - } - - func getDateString(fullLength: Bool) -> String { - let formatter = DateFormatter() - formatter.dateFormat = "EEEE, MMMM d" - formatter.timeZone = .autoupdatingCurrent - - let startString = "\(formatter.string(from: start))" - let endString = "\(formatter.string(from: end))" - - if (startString == endString) { - formatter.dateFormat = "EEEE, MMMM d" - let date = "\(formatter.string(from: start))" - return date - } - return startString + " to " + endString - } -} - -extension CalendarEvent: Equatable { static func == (lhs: CalendarEvent, rhs: CalendarEvent) -> Bool { - return lhs.name == rhs.name - && lhs.start == rhs.start - && lhs.end == rhs.end + return lhs.event == rhs.event } } diff --git a/PennMobile/Home/Cells/Calendar/HomeCalendarCellItem.swift b/PennMobile/Home/Cells/Calendar/HomeCalendarCellItem.swift index 6af400ffb..7f96b47f0 100644 --- a/PennMobile/Home/Cells/Calendar/HomeCalendarCellItem.swift +++ b/PennMobile/Home/Cells/Calendar/HomeCalendarCellItem.swift @@ -40,16 +40,3 @@ final class HomeCalendarCellItem: HomeCellItem { return events == item.events } } - -// MARK: - API Fetching -extension HomeCalendarCellItem: HomeAPIRequestable { - func fetchData(_ completion: @escaping () -> Void) { - CalendarAPI.instance.fetchCalendar { events in - if let top2Events = events?.prefix(2) { - self.events = Array(top2Events) - } - completion() - } - } -} - diff --git a/PennMobile/Home/Cells/Calendar/UniversityNotificationCell.swift b/PennMobile/Home/Cells/Calendar/UniversityNotificationCell.swift index 27b094692..a6c73533a 100644 --- a/PennMobile/Home/Cells/Calendar/UniversityNotificationCell.swift +++ b/PennMobile/Home/Cells/Calendar/UniversityNotificationCell.swift @@ -40,8 +40,8 @@ class UniversityNotificationCell: UITableViewCell { extension UniversityNotificationCell { fileprivate func setupCell(with calendarEvent: CalendarEvent) { backgroundColor = .clear - eventLabel.text = calendarEvent.name - dateLabel.text = calendarEvent.getDateString(fullLength: false) + eventLabel.text = calendarEvent.event + dateLabel.text = calendarEvent.date } } diff --git a/PennMobile/Home/Cells/Dining/HomeDiningCellItem.swift b/PennMobile/Home/Cells/Dining/HomeDiningCellItem.swift index 2d79a0a7f..e55016b12 100644 --- a/PennMobile/Home/Cells/Dining/HomeDiningCellItem.swift +++ b/PennMobile/Home/Cells/Dining/HomeDiningCellItem.swift @@ -31,11 +31,16 @@ final class HomeDiningCellItem: HomeCellItem { } static func getHomeCellItem(_ completion: @escaping((_ items: [HomeCellItem]) -> Void)) { - DiningAPI.instance.fetchDiningHours { _ in - let venues = DiningAPI.instance.getVenues() - let instance = HomeDiningCellItem(for: venues) - - completion([instance]) + UserDBManager.shared.fetchDiningPreferences { result in + if let venues = try? result.get() { + if venues.count == 0 { + completion([HomeDiningCellItem(for: DiningAPI.instance.getVenues(with: DiningVenue.defaultVenueIds))]) + } else { + completion([HomeDiningCellItem(for: venues)]) + } + } else { + completion([]) + } } } } diff --git a/PennMobile/Home/Cells/Feature/HomeFeatureCellItem.swift b/PennMobile/Home/Cells/Feature/HomeFeatureCellItem.swift index 8690a817c..935b473b9 100644 --- a/PennMobile/Home/Cells/Feature/HomeFeatureCellItem.swift +++ b/PennMobile/Home/Cells/Feature/HomeFeatureCellItem.swift @@ -26,11 +26,6 @@ final class HomeFeatureCellItem: HomeCellItem { self.announcement = announcement } - static func getItem(for json: JSON?) -> HomeCellItem? { - guard let json = json else { return nil } - return try? HomeFeatureCellItem(json: json) - } - static var associatedCell: ModularTableViewCell.Type { return HomeFeatureCell.self } @@ -40,21 +35,3 @@ final class HomeFeatureCellItem: HomeCellItem { return announcement.title == item.announcement.title } } - -// MARK: - HomeAPIRequestable -extension HomeFeatureCellItem: HomeAPIRequestable { - func fetchData(_ completion: @escaping () -> Void) { - ImageNetworkingManager.instance.downloadImage(imageUrl: announcement.imageUrl) { (image) in - self.image = image - completion() - } - } -} - -// MARK: - JSON Parsing -extension HomeFeatureCellItem { - convenience init(json: JSON) throws { - let announcement = try FeatureAnnouncement(json: json) - self.init(announcement: announcement) - } -} diff --git a/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift b/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift index 13f1647a0..40164acb2 100644 --- a/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift +++ b/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift @@ -17,7 +17,7 @@ final class HomeGSRLocationsCellItem: HomeCellItem { OAuth2NetworkManager.instance.getAccessToken { token in if let token = token { - let request = URLRequest(url: URL(string: "https://studentlife.pennlabs.org/penndata/gsrs/")!, accessToken: token) + let request = URLRequest(url: URL(string: "https://pennmobile.com/api/gsr/recent/")!, accessToken: token) let task = URLSession.shared.dataTask(with: request) { data, response, error in diff --git a/PennMobile/Home/Cells/GSR/HomeGSRCellItem.swift b/PennMobile/Home/Cells/GSR/HomeGSRCellItem.swift index 6f601b4f2..9ab03cd94 100644 --- a/PennMobile/Home/Cells/GSR/HomeGSRCellItem.swift +++ b/PennMobile/Home/Cells/GSR/HomeGSRCellItem.swift @@ -35,74 +35,4 @@ final class HomeGSRCellItem: HomeCellItem { static var jsonKey: String { return "studyRoomBooking" } - - static func getItem(for json: JSON?) -> HomeCellItem? { - guard let _ = json else { return nil } - return HomeGSRCellItem() - } -} - -// MARK: - API Fetching and Parsing -extension HomeGSRCellItem: HomeAPIRequestable { - func fetchData(_ completion: @escaping () -> Void) { - - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - - _ = formatter.string(from: Date().roundedDownToHour) - -// GSRNetworkManager.instance.getAvailability(for: 1086, dateStr: dateString) { (rooms) in -// if let rooms = rooms { -// self.filterForTimeConstraints(rooms) -// } -// completion() -// } - } - - private func filterForTimeConstraints(_ rooms: [GSRRoom]) { -// let wicRooms = rooms.filter { $0.id == 1889 && $0.roomName.contains("Rm") } -// let wicBooths = rooms.filter { $0.id == 1889 && $0.roomName.contains("Booth") } -// -// let gsrLoc = GSRLocation(lid: 1086, gid: 1889, name: "Weigle", kind: "LIBCAL", imageUrl: "URL") -// var roomBookings = [GSRBooking?]() -// for i in 1...3 { -// let roomSlot = getFirstOpenRoom(wicRooms, duration: 30*i) -// roomBookings.append(getBooking(gsrLoc, roomSlot.0, 1, roomSlot.1)) -// } -// var boothBookings = [GSRBooking?]() -// for i in 1...3 { -// let roomSlot = getFirstOpenRoom(wicBooths, duration: 30*i) -// boothBookings.append(getBooking(gsrLoc, roomSlot.0, 1, roomSlot.1)) -// } -// self.bookingOptions = [boothBookings, roomBookings] - } - -// private func getBooking(_ location: GSRLocation, _ slot: GSRTimeSlot?, _ numSlots: Int, _ room: GSRRoom?) -> GSRBooking? { -// guard let slot = slot else { return nil } -// var endTime = slot.endTime -// if numSlots == 2 { -// guard let lastSlot = slot.next else { return nil } -// endTime = lastSlot.endTime -// } -// if numSlots == 3 { -// guard let _ = slot.next else { return nil } -// guard let lastSlot = slot.next!.next else { return nil } -// endTime = lastSlot.endTime -// } -// return GSRBooking(location: location, roomId: slot.roomId, start: slot.startTime, end: endTime, name: room?.name) -// } -// -// private func getFirstOpenRoom(_ rooms: [GSRRoom], duration: Int) -> (GSRTimeSlot?, GSRRoom?) { -// let firstOpenRoom: GSRRoom? = rooms.min { (lhs, rhs) -> Bool in -// guard let lhsFirst = lhs.timeSlots.firstTimeslot(duration: duration) else { -// return false -// } -// guard let rhsFirst = rhs.timeSlots.firstTimeslot(duration: duration) else { -// return true -// } -// return lhsFirst.startTime <= rhsFirst.startTime -// } -// let firstTimeSlot : GSRTimeSlot? = firstOpenRoom?.timeSlots.firstTimeslot(duration: duration) -// return (firstTimeSlot, firstOpenRoom) -// } } diff --git a/PennMobile/Home/Cells/Laundry/HomeLaundryCellItem.swift b/PennMobile/Home/Cells/Laundry/HomeLaundryCellItem.swift index 701e16078..5a32c9754 100644 --- a/PennMobile/Home/Cells/Laundry/HomeLaundryCellItem.swift +++ b/PennMobile/Home/Cells/Laundry/HomeLaundryCellItem.swift @@ -15,7 +15,19 @@ final class HomeLaundryCellItem: HomeCellItem { } static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { - completion([]) + UserDBManager.shared.getLaundryPreferences { result in + if let ids = result, ids.count > 0 { + LaundryAPIService.instance.fetchLaundryData(for: ids) { rooms in + if let rooms = rooms, rooms.count > 0 { + completion([HomeLaundryCellItem(room: rooms[0])]) + } else { + completion([]) + } + } + } else { + completion([]) + } + } } var room: LaundryRoom @@ -54,17 +66,3 @@ extension HomeLaundryCellItem { self.init(room: room) } } - -// MARK: - API Fetching -extension HomeLaundryCellItem: HomeAPIRequestable { - func fetchData(_ completion: @escaping () -> Void) { - LaundryNotificationCenter.shared.updateForExpiredNotifications { - LaundryAPIService.instance.fetchLaundryData(for: [self.room]) { (rooms) in - if let room = rooms?.first { - self.room = room - } - completion() - } - } - } -} diff --git a/PennMobile/Home/Cells/News/HomeNewsCellItem.swift b/PennMobile/Home/Cells/News/HomeNewsCellItem.swift index 3692bf27e..391493f46 100644 --- a/PennMobile/Home/Cells/News/HomeNewsCellItem.swift +++ b/PennMobile/Home/Cells/News/HomeNewsCellItem.swift @@ -20,7 +20,7 @@ final class HomeNewsCellItem: HomeCellItem { } static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { - let task = URLSession.shared.dataTask(with: URL(string: "https://studentlife.pennlabs.org/penndata/news/")!) { data, response, error in + let task = URLSession.shared.dataTask(with: URL(string: "https://pennmobile.org/api/penndata/news/")!) { data, response, error in guard let data = data else { completion([]); return } if let article = try? JSONDecoder().decode(NewsArticle.self, from: data) { diff --git a/PennMobile/Home/Cells/Post/HomePostCellItem.swift b/PennMobile/Home/Cells/Post/HomePostCellItem.swift index 4c69e4f94..4431f8164 100644 --- a/PennMobile/Home/Cells/Post/HomePostCellItem.swift +++ b/PennMobile/Home/Cells/Post/HomePostCellItem.swift @@ -8,10 +8,23 @@ import Foundation import SwiftyJSON +import UIKit final class HomePostCellItem: HomeCellItem { static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { - completion([]) + let url = URL(string: "https://pennmobile.org/api/portal/posts/")! + + let task = URLSession.shared.dataTask(with: url) { data, response, error in + guard let data = data else { completion([]); return } + + if let article = try? JSONDecoder().decode(NewsArticle.self, from: data) { + completion([HomeNewsCellItem(for: article)]) + } else { + completion([]) + } + } + + task.resume() } static var jsonKey: String { @@ -25,11 +38,6 @@ final class HomePostCellItem: HomeCellItem { self.post = post } - static func getItem(for json: JSON?) -> HomeCellItem? { - guard let json = json else { return nil } - return try? HomePostCellItem(json: json) - } - static var associatedCell: ModularTableViewCell.Type { return HomePostCell.self } @@ -40,24 +48,6 @@ final class HomePostCellItem: HomeCellItem { } } -// MARK: - HomeAPIRequestable -extension HomePostCellItem: HomeAPIRequestable { - func fetchData(_ completion: @escaping () -> Void) { - ImageNetworkingManager.instance.downloadImage(imageUrl: post.imageUrl) { (image) in - self.image = image - completion() - } - } -} - -// MARK: - JSON Parsing -extension HomePostCellItem { - convenience init(json: JSON) throws { - let post = try Post(json: json) - self.init(post: post) - } -} - // MARK: - Logging ID extension HomePostCellItem: LoggingIdentifiable { var id: String { diff --git a/PennMobile/Home/Controllers/HomeViewController.swift b/PennMobile/Home/Controllers/HomeViewController.swift index a2ab0a1a6..4b82470ca 100644 --- a/PennMobile/Home/Controllers/HomeViewController.swift +++ b/PennMobile/Home/Controllers/HomeViewController.swift @@ -102,17 +102,15 @@ class HomeViewController: GenericViewController { // MARK: - Home Page Networking extension HomeViewController { - fileprivate func refreshTableView(_ completion: (() -> Void)? = nil) { + fileprivate func refreshTableView(forceRefresh: Bool = false, _ completion: (() -> Void)? = nil) { let now = Date() - if tableViewModel == nil || now > lastRefresh.add(minutes: HomeViewController.refreshInterval) { + if forceRefresh || tableViewModel == nil || now > lastRefresh.add(minutes: HomeViewController.refreshInterval) { fetchViewModel { self.lastRefresh = Date() completion?() } } else { - // Reload visibile cell, then get data for each cell, and reload again - self.tableView.reloadData() - self.fetchAllCellData(completion) + completion?() } } } @@ -187,34 +185,13 @@ extension HomeViewController { } } - func fetchAllCellData(_ completion: (() -> Void)? = nil) { - fetchCellData(for: HomeItemTypes.instance.getAllTypes(), completion) - } - - func fetchCellData(for itemTypes: [HomeCellItem.Type], _ completion: (() -> Void)? = nil) { - let items = tableViewModel.getItems(for: itemTypes) - self.fetchCellData(for: items, completion) - } - - func fetchCellData(for items: [HomeCellItem], _ completion: (() -> Void)? = nil) { - HomeAsynchronousAPIFetching.instance.fetchData(for: items, singleCompletion: { (item) in - DispatchQueue.main.async { - self.reloadItem(item) - } - }) { - DispatchQueue.main.async { - completion?() - } - } - } - func reloadItem(_ item: HomeCellItem) { guard let allItems = tableViewModel?.items as? [HomeCellItem] else { return } if let row = allItems.firstIndex(where: { (thisItem) -> Bool in thisItem.equals(item: item) }) { let indexPath = IndexPath(row: row, section: 0) - self.tableView.reloadRows(at: [indexPath], with: .none) + self.tableView.reloadRows(at: [indexPath], with: .fade) } } @@ -238,7 +215,7 @@ extension HomeViewController { } @objc fileprivate func handleRefresh(_ sender: Any) { - self.refreshTableView { + self.refreshTableView(forceRefresh: true) { self.tableView.refreshControl?.endRefreshing() } } @@ -254,7 +231,6 @@ extension HomeViewController : DiningCellSettingsDelegate { } reloadItem(diningItem) - self.fetchCellData(for: [diningItem]) UserDBManager.shared.saveDiningPreference(for: venueIds) } } @@ -279,7 +255,7 @@ extension HomeViewController { } } - self.fetchCellData(for: [HomeItemTypes.instance.laundry]) +// self.reloadItem(<#T##HomeCellItem#>)(for: [HomeItemTypes.instance.laundry]) } } diff --git a/PennMobile/Home/Model/HomeCellItem.swift b/PennMobile/Home/Model/HomeCellItem.swift index 4cc2d0395..a9e5c8fff 100644 --- a/PennMobile/Home/Model/HomeCellItem.swift +++ b/PennMobile/Home/Model/HomeCellItem.swift @@ -11,9 +11,16 @@ import SwiftyJSON protocol HomeCellItem: ModularTableViewItem { static var jsonKey: String { get } - static func getHomeCellItem(_ completion: @escaping((_ item: [HomeCellItem]) -> Void)) + static func getHomeCellItem(_ completion: @escaping((_ item: [HomeCellItem]) -> Void)) } +extension HomeCellItem { + var cellIdentifier: String { + return Self.jsonKey + } +} + + protocol LoggingIdentifiable where Self: HomeCellItem { var id: String { get } } diff --git a/PennMobile/Home/Model/HomeItemTypes.swift b/PennMobile/Home/Model/HomeItemTypes.swift index 13614a79d..124768f01 100644 --- a/PennMobile/Home/Model/HomeItemTypes.swift +++ b/PennMobile/Home/Model/HomeItemTypes.swift @@ -24,7 +24,7 @@ final class HomeItemTypes: ModularTableViewItemTypes { let courses: HomeCellItem.Type = HomeCoursesCellItem.self let gsrLocations: HomeCellItem.Type = HomeGSRLocationsCellItem.self let invites: HomeCellItem.Type = HomeGroupInvitesCellItem.self -// let update: HomeCellItem.Type = HomeUpdateCellItem.self + //let update: HomeCellItem.Type = HomeUpdateCellItem.self } // MARK: - JSON Parsing @@ -47,29 +47,3 @@ extension HomeItemTypes { return mirror.children.map { $0.value as! HomeCellItem.Type } } } - -// MARK: Default Cells for Development Purposes -extension HomeItemTypes { - /** - * Purpose: For building a new cell that is not yet on the API - * Usage: 1) Add cell type to HomeItemTypes - * 2) Append cell type to types array below - * 3) Initialize item in its class (ex: HomeNewsCellItem) - * - * Ex: (1) let news: HomeCellItem.Type = HomeNewsCellItem.self (in HomeItemTypes) - * - * (2) var types = [HomeCellItem.Type]() - * types.append(news) - * return types - * - * (3) static func getItem(for json: JSON?) -> HomeCellItem? { - * return HomeNewsCellItem() - * } - * - * Note: This method should return an empty array when the app is in production - **/ - func getDefaultItems() -> [HomeCellItem.Type] { - let types = [HomeCellItem.Type]() - return types - } -} diff --git a/PennMobile/Home/Networking/HomeAPIService.swift b/PennMobile/Home/Networking/HomeAPIService.swift index 862fb0ac4..3fdf9eaf0 100644 --- a/PennMobile/Home/Networking/HomeAPIService.swift +++ b/PennMobile/Home/Networking/HomeAPIService.swift @@ -9,7 +9,7 @@ import Foundation import SwiftyJSON -final class HomeAPIService { +final class HomeAPIService: Requestable { static let instance = HomeAPIService() func fetchModel(_ completion: @escaping ((HomeTableViewModel) -> Void)) { @@ -29,8 +29,25 @@ final class HomeAPIService { } } + group.enter() + var rankingDict: [String: Int] = [:] + getRequestData(url: "https://pennmobile.org/api/penndata/order/") { (data, _, _) in + guard let data = data else { group.leave(); return } + for e in JSON(data).array ?? [JSON]() { + if let cell = e["cell"].string, let ranking = e["rank"].int { + rankingDict[cell] = ranking + } + } + + group.leave() + } + // Handle completion of model after it is done group.notify(queue: .main) { + if let homeItems = model.items as? [HomeCellItem] { + model.items = homeItems.sorted(by: {rankingDict[$0.cellIdentifier] ?? -1 > rankingDict[$1.cellIdentifier] ?? -1}) + } + completion(model) } } diff --git a/PennMobile/Laundry/Controllers/LaundryTableViewController.swift b/PennMobile/Laundry/Controllers/LaundryTableViewController.swift index 75609a662..74f757127 100644 --- a/PennMobile/Laundry/Controllers/LaundryTableViewController.swift +++ b/PennMobile/Laundry/Controllers/LaundryTableViewController.swift @@ -6,6 +6,8 @@ // Copyright © 2017 PennLabs. All rights reserved. // +import UIKit + class LaundryTableViewController: GenericTableViewController, IndicatorEnabled, ShowsAlert, NotificationRequestable { internal var rooms = [LaundryRoom]() diff --git a/PennMobile/Laundry/Networking/LaundryAPIService.swift b/PennMobile/Laundry/Networking/LaundryAPIService.swift index c61521762..f9b0432f6 100644 --- a/PennMobile/Laundry/Networking/LaundryAPIService.swift +++ b/PennMobile/Laundry/Networking/LaundryAPIService.swift @@ -12,9 +12,9 @@ class LaundryAPIService: Requestable { static let instance = LaundryAPIService() - fileprivate let laundryUrl = "https://api.pennlabs.org/laundry/rooms" - fileprivate let idsUrl = "https://api.pennlabs.org/laundry/halls/ids" - fileprivate let statusURL = "https://api.pennlabs.org/laundry/status" + fileprivate let laundryUrl = "https://pennmobile.org/api/laundry/rooms" + fileprivate let idsUrl = "https://pennmobile.org/api/laundry/halls/ids" + fileprivate let statusURL = "https://pennmobile.org/api/laundry/status" public var idToRooms: [Int: LaundryRoom]? @@ -61,8 +61,8 @@ class LaundryAPIService: Requestable { // MARK: - Fetch API extension LaundryAPIService { - func fetchLaundryData(for rooms: [LaundryRoom], _ callback: @escaping (_ rooms: [LaundryRoom]?) -> Void) { - let ids: String = rooms.map { $0.id }.map { String($0) }.joined(separator: ",") + func fetchLaundryData(for ids: [Int], _ callback: @escaping (_ rooms: [LaundryRoom]?) -> Void) { + let ids: String = ids.map { String($0) }.joined(separator: ",") let url = "\(laundryUrl)/\(ids)" getRequest(url: url) { (dict, error, statusCode) in var rooms: [LaundryRoom]? @@ -78,6 +78,11 @@ extension LaundryAPIService { callback(rooms) } } + + func fetchLaundryData(for rooms: [LaundryRoom], _ callback: @escaping (_ rooms: [LaundryRoom]?) -> Void) { + let ids: [Int] = rooms.map { $0.id } + fetchLaundryData(for: ids, callback) + } } // MARK: - Laundry Status API diff --git a/PennMobile/Login/CampusExpressNetworkManager.swift b/PennMobile/Login/CampusExpressNetworkManager.swift index 19147133e..24cea1688 100644 --- a/PennMobile/Login/CampusExpressNetworkManager.swift +++ b/PennMobile/Login/CampusExpressNetworkManager.swift @@ -53,4 +53,22 @@ extension CampusExpressNetworkManager: PennAuthRequestable { } } } + + // TODO: - Finish dining balance fix + func test() { + makeAuthRequest(targetUrl: diningUrl, shibbolethUrl: shibbolethUrl) { (data, response, error) in + if let data = data, let html = String(data: data, encoding: .utf8) { + if let doc = try? SwiftSoup.parse(html), let elementsText = try? doc.getElementsByClass("positive-value").text().split(separator: " "), elementsText.count >= 4 { + var diningBalance = elementsText[0] + diningBalance.removeFirst() + print(diningBalance) + print(elementsText[1]) + print(elementsText[2]) + print(elementsText[3]) + } + } else { + + } + } + } } diff --git a/PennMobile/Setup + Navigation/AppDelegate.swift b/PennMobile/Setup + Navigation/AppDelegate.swift index eca74b59a..e89b2a6d8 100644 --- a/PennMobile/Setup + Navigation/AppDelegate.swift +++ b/PennMobile/Setup + Navigation/AppDelegate.swift @@ -17,14 +17,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var tabBarController: TabBarController! - static var shared: AppDelegate { - return UIApplication.shared.delegate as! AppDelegate - } - - var rootViewController: RootViewController { - return window!.rootViewController as! RootViewController - } - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { UserDefaults.standard.set(gsrGroupsEnabled: false) @@ -57,7 +49,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var backgroundTaskID: UIBackgroundTaskIdentifier? func applicationDidEnterBackground(_ application: UIApplication) { - if FeedAnalyticsManager.shared.dryRun { return } sendLogsToServer() } @@ -106,6 +97,17 @@ extension AppDelegate: OnboardingDelegate { } } +// Global access of rootview to handle navigations +extension AppDelegate { + static var shared: AppDelegate { + return UIApplication.shared.delegate as! AppDelegate + } + + var rootViewController: RootViewController { + return window!.rootViewController as! RootViewController + } +} + // Helper function inserted by Swift 4.2 migrator. fileprivate func convertFromUIBackgroundTaskIdentifier(_ input: UIBackgroundTaskIdentifier) -> Int { return input.rawValue diff --git a/PennMobile/Setup + Navigation/RootViewController.swift b/PennMobile/Setup + Navigation/RootViewController.swift index 443ef2873..64504c8b4 100644 --- a/PennMobile/Setup + Navigation/RootViewController.swift +++ b/PennMobile/Setup + Navigation/RootViewController.swift @@ -33,6 +33,7 @@ class RootViewController: UIViewController, NotificationRequestable { if let rooms = UserDefaults.standard.getLaundryPreferences() { UserDBManager.shared.saveLaundryPreferences(for: rooms) } + if UserDefaults.standard.getPreference(for: .anonymizedCourseSchedule) { // Update course anonymization key if privacy option is TRUE // Pennkey-Password key has been updated to be option-specific diff --git a/PennMobile/Setup + Navigation/SplashViewController.swift b/PennMobile/Setup + Navigation/SplashViewController.swift index 137079f6d..85acce9fb 100644 --- a/PennMobile/Setup + Navigation/SplashViewController.swift +++ b/PennMobile/Setup + Navigation/SplashViewController.swift @@ -14,8 +14,22 @@ class SplashViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .red - switchToView() + view.backgroundColor = .uiBackground + + if #available(iOS 15.0, *) { + Task { + do { + let (data, _) = try await URLSession.shared.data(from: URL(string: "https://google.com")!) + print(String(data: data, encoding: .utf8)) + } catch { + print("error?") + + } + switchToView() + } + } else { + switchToView() + } } private func switchToView() { diff --git a/Podfile.lock b/Podfile.lock index 93cf4e644..14376c527 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -37,4 +37,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 9e5e28b991a790112963ed77c95535421e3a89ff -COCOAPODS: 1.11.2 +COCOAPODS: 1.10.1 From f8455d15fbc4a6055e99bc1c49a1c709092f5926 Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Fri, 21 Jan 2022 21:05:25 -0500 Subject: [PATCH 07/21] added event image icon --- PennMobile.xcodeproj/project.pbxproj | 54 +++++++++++------- .../Event.imageset/Contents.json | 23 ++++++++ .../Event.imageset/Event@1x.png | Bin 0 -> 4407 bytes .../Event.imageset/Event@2x.png | Bin 0 -> 8845 bytes .../Event.imageset/Event@3x.png | Bin 0 -> 11577 bytes .../Setup + Navigation/ControllerModel.swift | 6 +- Podfile.lock | 2 +- 7 files changed, 60 insertions(+), 25 deletions(-) create mode 100644 PennMobile/Assets.xcassets/More Tab Icons/Event.imageset/Contents.json create mode 100644 PennMobile/Assets.xcassets/More Tab Icons/Event.imageset/Event@1x.png create mode 100644 PennMobile/Assets.xcassets/More Tab Icons/Event.imageset/Event@2x.png create mode 100644 PennMobile/Assets.xcassets/More Tab Icons/Event.imageset/Event@3x.png diff --git a/PennMobile.xcodeproj/project.pbxproj b/PennMobile.xcodeproj/project.pbxproj index 561be5621..a4d62192c 100644 --- a/PennMobile.xcodeproj/project.pbxproj +++ b/PennMobile.xcodeproj/project.pbxproj @@ -138,8 +138,8 @@ 66E8ECA72381CB5100945BEA /* TwoFactorTokenGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E8ECA62381CB5100945BEA /* TwoFactorTokenGenerator.swift */; }; 66E8ECA92381CD2F00945BEA /* TOTPNetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E8ECA82381CD2F00945BEA /* TOTPNetworkManager.swift */; }; 6C11C08026F842CF00407C04 /* HomeUpdateCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C11C07F26F842CF00407C04 /* HomeUpdateCellItem.swift */; }; - 6C23AF9526E57903002F60F0 /* (null) in Sources */ = {isa = PBXBuildFile; }; - 6C369A1526E39BC100721CA1 /* (null) in Sources */ = {isa = PBXBuildFile; }; + 6C23AF9526E57903002F60F0 /* BuildFile in Sources */ = {isa = PBXBuildFile; }; + 6C369A1526E39BC100721CA1 /* BuildFile in Sources */ = {isa = PBXBuildFile; }; 6C4CC1FA26E6B1720000B4A8 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 6C4CC1F926E6B1720000B4A8 /* SwiftyJSON */; }; 6C6035F326E722890025FBC7 /* DiningVenueDetailLocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6035ED26E722890025FBC7 /* DiningVenueDetailLocationView.swift */; }; 6C6035F426E722890025FBC7 /* DiningVenueDetailMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6035EE26E722890025FBC7 /* DiningVenueDetailMenuView.swift */; }; @@ -1822,8 +1822,8 @@ F213CCE023C3EE3E000AD90F /* XCRemoteSwiftPackageReference "SwiftSoup" */, F213CCE323C3F240000AD90F /* XCRemoteSwiftPackageReference "Kingfisher" */, F2568A742413534F00561295 /* XCRemoteSwiftPackageReference "SnapKit" */, - 6C4CC1F826E6B1720000B4A8 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, - 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + 6C4CC1F826E6B1720000B4A8 /* XCRemoteSwiftPackageReference "SwiftyJSON.git" */, + 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk.git" */, ); productRefGroup = 216640611EBADADA00746B8E /* Products */; projectDirPath = ""; @@ -1893,12 +1893,13 @@ buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-AutomatedScreenshotUITests/Pods-AutomatedScreenshotUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-AutomatedScreenshotUITests/Pods-AutomatedScreenshotUITests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/SimulatorStatusMagic/SimulatorStatusMagic.framework", ); name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-AutomatedScreenshotUITests/Pods-AutomatedScreenshotUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SimulatorStatusMagic.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1949,12 +1950,23 @@ buildActionMask = 12; files = ( ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PennMobile/Pods-PennMobile-frameworks-${CONFIGURATION}-input-files.xcfilelist", + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PennMobile/Pods-PennMobile-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Base32/Base32.framework", + "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework", + "${BUILT_PRODUCTS_DIR}/OneTimePassword/OneTimePassword.framework", + "${BUILT_PRODUCTS_DIR}/SCLAlertView/SCLAlertView.framework", + "${BUILT_PRODUCTS_DIR}/ScrollableGraphView/ScrollableGraphView.framework", + "${BUILT_PRODUCTS_DIR}/XLPagerTabStrip/XLPagerTabStrip.framework", ); name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PennMobile/Pods-PennMobile-frameworks-${CONFIGURATION}-output-files.xcfilelist", + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Base32.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OneTimePassword.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SCLAlertView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ScrollableGraphView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/XLPagerTabStrip.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -2029,7 +2041,7 @@ 21B8BBB82223963C00EBC1D0 /* Degree.swift in Sources */, 6CAE4390253370E600BD0200 /* DiningVenueView.swift in Sources */, 6CAE4383253370E500BD0200 /* DailyAverageView.swift in Sources */, - 6C369A1526E39BC100721CA1 /* (null) in Sources */, + 6C369A1526E39BC100721CA1 /* BuildFile in Sources */, 2119D26922529A2300693CDB /* HomeGSRLocationsCell.swift in Sources */, 21D5E07623BD45F400B331CC /* KeychainAccessible.swift in Sources */, 21470EBC223DD34200019C10 /* ScheduleEventCell.swift in Sources */, @@ -2133,7 +2145,7 @@ EFE2D6EF239B11050020F6BF /* GroupIndividualSettingView.swift in Sources */, EF6329A22409D2CE00E7ED36 /* GSRGroupConfirmBookingController.swift in Sources */, 21D5E07423BCFE0300B331CC /* CoursePrivacyController.swift in Sources */, - 6C23AF9526E57903002F60F0 /* (null) in Sources */, + 6C23AF9526E57903002F60F0 /* BuildFile in Sources */, B658393B1FABD295009486FC /* LaundryAPIService.swift in Sources */, 6C6035F626E722890025FBC7 /* DiningVenueDetailView.swift in Sources */, 21ABE2A9223D7DEA00199D29 /* ScheduleLayout.swift in Sources */, @@ -2429,7 +2441,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CF_BUNDLE_LONG_VERSION_STRING"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = VU59R57FGM; EXCLUDED_ARCHS = ""; GCC_PREFIX_HEADER = PennMobile/Supporting_Files/PrefixHeader.pch; INFOPLIST_FILE = PennMobile/Supporting_Files/Info.plist; @@ -2462,7 +2474,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CF_BUNDLE_LONG_VERSION_STRING"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = VU59R57FGM; EXCLUDED_ARCHS = ""; GCC_PREFIX_HEADER = PennMobile/Supporting_Files/PrefixHeader.pch; INFOPLIST_FILE = PennMobile/Supporting_Files/Info.plist; @@ -2636,7 +2648,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 6C4CC1F826E6B1720000B4A8 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { + 6C4CC1F826E6B1720000B4A8 /* XCRemoteSwiftPackageReference "SwiftyJSON.git" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON.git"; requirement = { @@ -2644,7 +2656,7 @@ minimumVersion = 5.0.1; }; }; - 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk.git" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { @@ -2681,7 +2693,7 @@ /* Begin XCSwiftPackageProductDependency section */ 6C4CC1F926E6B1720000B4A8 /* SwiftyJSON */ = { isa = XCSwiftPackageProductDependency; - package = 6C4CC1F826E6B1720000B4A8 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; + package = 6C4CC1F826E6B1720000B4A8 /* XCRemoteSwiftPackageReference "SwiftyJSON.git" */; productName = SwiftyJSON; }; 6CA1ACDA271D2D5000EDB967 /* Kingfisher */ = { @@ -2691,12 +2703,12 @@ }; 6CE12F8F26E82DC600284D9F /* FirebaseAnalytics */ = { isa = XCSwiftPackageProductDependency; - package = 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + package = 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk.git" */; productName = FirebaseAnalytics; }; 6CE12F9126E82DC600284D9F /* FirebaseCrashlytics */ = { isa = XCSwiftPackageProductDependency; - package = 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + package = 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk.git" */; productName = FirebaseCrashlytics; }; F213CCE123C3EE3E000AD90F /* SwiftSoup */ = { diff --git a/PennMobile/Assets.xcassets/More Tab Icons/Event.imageset/Contents.json b/PennMobile/Assets.xcassets/More Tab Icons/Event.imageset/Contents.json new file mode 100644 index 000000000..eedd937a1 --- /dev/null +++ b/PennMobile/Assets.xcassets/More Tab Icons/Event.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Event@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Event@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Event@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PennMobile/Assets.xcassets/More Tab Icons/Event.imageset/Event@1x.png b/PennMobile/Assets.xcassets/More Tab Icons/Event.imageset/Event@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..08d75d48d1c89795b99aed5a466ce4a142d47ae8 GIT binary patch literal 4407 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 Op{p1}t#S=4?*9QDp4hkm literal 0 HcmV?d00001 diff --git a/PennMobile/Setup + Navigation/ControllerModel.swift b/PennMobile/Setup + Navigation/ControllerModel.swift index fc21fdf16..cedcbde28 100644 --- a/PennMobile/Setup + Navigation/ControllerModel.swift +++ b/PennMobile/Setup + Navigation/ControllerModel.swift @@ -85,7 +85,7 @@ class ControllerModel: NSObject { #if DEBUG return [.news, .contacts, .courseSchedule, .courseAlerts, .events, .about] #else - return [.news, .contacts, .courseSchedule, .about] + return [.news, .contacts, .courseSchedule, .events, .about] #endif } } @@ -95,9 +95,9 @@ class ControllerModel: NSObject { //courseAlerts should only show up in testflight but we should NEVER show in production, need to manually remove it in the future get { #if DEBUG - return [#imageLiteral(resourceName: "News"), #imageLiteral(resourceName: "Contacts"), #imageLiteral(resourceName: "Calendar Light"), #imageLiteral(resourceName: "PCA"), #imageLiteral(resourceName: "PCA"), #imageLiteral(resourceName: "logo-small")] + return [#imageLiteral(resourceName: "News"), #imageLiteral(resourceName: "Contacts"), #imageLiteral(resourceName: "Calendar Light"), #imageLiteral(resourceName: "PCA"), #imageLiteral(resourceName: "Event"), #imageLiteral(resourceName: "logo-small")] #else - return [#imageLiteral(resourceName: "News"), #imageLiteral(resourceName: "Contacts"), #imageLiteral(resourceName: "Calendar Light"), #imageLiteral(resourceName: "logo-small")] + return [#imageLiteral(resourceName: "News"), #imageLiteral(resourceName: "Contacts"), #imageLiteral(resourceName: "Calendar Light"), #imageLiteral(resourceName: "Event"), #imageLiteral(resourceName: "logo-small")] #endif } } diff --git a/Podfile.lock b/Podfile.lock index 93cf4e644..14376c527 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -37,4 +37,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 9e5e28b991a790112963ed77c95535421e3a89ff -COCOAPODS: 1.11.2 +COCOAPODS: 1.10.1 From ed92451396af41f20c18bced37ac4ff41ed709d9 Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Tue, 25 Jan 2022 09:55:11 -0500 Subject: [PATCH 08/21] migrated post to new api --- PennMobile/Home/Cells/Post/HomePostCell.swift | 7 +-- .../Home/Cells/Post/HomePostCellItem.swift | 28 +++++++---- PennMobile/Home/Cells/Post/Post.swift | 49 ++----------------- 3 files changed, 27 insertions(+), 57 deletions(-) diff --git a/PennMobile/Home/Cells/Post/HomePostCell.swift b/PennMobile/Home/Cells/Post/HomePostCell.swift index bf1c2f6a3..f4ed652f2 100644 --- a/PennMobile/Home/Cells/Post/HomePostCell.swift +++ b/PennMobile/Home/Cells/Post/HomePostCell.swift @@ -7,6 +7,7 @@ // import Foundation +import Kingfisher final class HomePostCell: UITableViewCell, HomeCellConformable { static var identifier: String = "homePostCell" @@ -101,11 +102,11 @@ final class HomePostCell: UITableViewCell, HomeCellConformable { extension HomePostCell { fileprivate func setupCell(with item: HomePostCellItem) { self.post = item.post - self.postImageView.image = item.image + postImageView.kf.setImage(with: URL(string: item.post.imageUrl)!) self.sourceLabel.text = post.source self.titleLabel.text = post.title self.subtitleLabel?.text = post.subtitle - self.dateLabel.text = post.timeLabel + self.dateLabel.text = post.createdDate if item.post.source == nil { titleTopConstraintToSource.isActive = false @@ -134,7 +135,7 @@ extension HomePostCell { @objc fileprivate func handleTapped(_ sender: Any) { guard let delegate = delegate as? URLSelectable, let url = post.postUrl else { return } let title = url.replacingOccurrences(of: "http://", with: "").replacingOccurrences(of: "https://", with: "").split(separator: "/").first! - delegate.handleUrlPressed(urlStr: url, title: String(title), item: self.item, shouldLog: !post.isTest) + delegate.handleUrlPressed(urlStr: url, title: String(title), item: self.item, shouldLog: true) } } diff --git a/PennMobile/Home/Cells/Post/HomePostCellItem.swift b/PennMobile/Home/Cells/Post/HomePostCellItem.swift index 4431f8164..9288bb1d1 100644 --- a/PennMobile/Home/Cells/Post/HomePostCellItem.swift +++ b/PennMobile/Home/Cells/Post/HomePostCellItem.swift @@ -12,19 +12,28 @@ import UIKit final class HomePostCellItem: HomeCellItem { static func getHomeCellItem(_ completion: @escaping (([HomeCellItem]) -> Void)) { - let url = URL(string: "https://pennmobile.org/api/portal/posts/")! + OAuth2NetworkManager.instance.getAccessToken { token in + guard let token = token else { completion([]); return } - let task = URLSession.shared.dataTask(with: url) { data, response, error in - guard let data = data else { completion([]); return } + let url = URLRequest(url: URL(string: "https://pennmobile.org/api/portal/posts/")!, accessToken: token) - if let article = try? JSONDecoder().decode(NewsArticle.self, from: data) { - completion([HomeNewsCellItem(for: article)]) - } else { - completion([]) + let task = URLSession.shared.dataTask(with: url) { data, response, error in + guard let data = data else { completion([]); return } + + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + decoder.dateDecodingStrategy = .iso8601 + + if let posts = try? decoder.decode([Post].self, from: data) { + print(posts) + completion(posts.map({return HomePostCellItem(post: $0)})) + } else { + completion([]) + } } + + task.resume() } - - task.resume() } static var jsonKey: String { @@ -32,7 +41,6 @@ final class HomePostCellItem: HomeCellItem { } let post: Post - var image: UIImage? init(post: Post) { self.post = post diff --git a/PennMobile/Home/Cells/Post/Post.swift b/PennMobile/Home/Cells/Post/Post.swift index a8b1bb47f..d8f071177 100644 --- a/PennMobile/Home/Cells/Post/Post.swift +++ b/PennMobile/Home/Cells/Post/Post.swift @@ -7,52 +7,13 @@ // import Foundation -import SwiftyJSON -class Post { - let source: String? +struct Post: Codable { + let id: Int let title: String? let subtitle: String? - let timeLabel: String? - let imageUrl: String let postUrl: String? - let id: Int - let isTest: Bool - - init(source: String?, title: String?, subtitle: String?, timeLabel: String?, imageUrl: String, postUrl: String?, id: Int, isTest: Bool) { - self.source = source - self.title = title - self.subtitle = subtitle - self.timeLabel = timeLabel - self.imageUrl = imageUrl - self.postUrl = postUrl - self.id = id - self.isTest = isTest - } -} - -// MARK: - JSON Parsing -extension Post { - convenience init(json: JSON) throws { - guard let imageUrl = json["image_url"].string, let id = json["post_id"].int, let isTest = json["test"].bool else { - // All posts must have at least an image, an id, and a test flag - throw NetworkingError.jsonError - } - - let source = json["source"].string - let title = json["title"].string - let subtitle = json["subtitle"].string - let postUrl = json["post_url"].string - let timeLabel = json["time_label"].string - - if (source == nil && timeLabel != nil) || (title == nil && subtitle == nil && source != nil) || (title == nil && subtitle != nil) { - // Rules: - // (1) A time label cannot exist without a source label - // (2) An image cannot be accompanied with only a source label - // (3) A subtitle cannot exist without a title - throw NetworkingError.jsonError - } - - self.init(source: source, title: title, subtitle: subtitle, timeLabel: timeLabel, imageUrl: imageUrl, postUrl: postUrl, id: id, isTest: isTest) - } + let imageUrl: String + let createdDate: String? + let source: String? } From 0afbf772701fc95063f4bddbbdd96a21c82dbe6d Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Tue, 25 Jan 2022 15:44:19 -0500 Subject: [PATCH 09/21] fixed tab bar color issue --- .../Setup + Navigation/AppDelegate.swift | 1 - .../Setup + Navigation/ControllerModel.swift | 20 ----------- .../Setup + Navigation/TabBarController.swift | 34 +++++++++---------- 3 files changed, 16 insertions(+), 39 deletions(-) diff --git a/PennMobile/Setup + Navigation/AppDelegate.swift b/PennMobile/Setup + Navigation/AppDelegate.swift index e89b2a6d8..6af88f543 100644 --- a/PennMobile/Setup + Navigation/AppDelegate.swift +++ b/PennMobile/Setup + Navigation/AppDelegate.swift @@ -80,7 +80,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func applicationWillEnterForeground(_ application: UIApplication) { - tabBarController?.reloadTabs() rootViewController.applicationWillEnterForeground() } diff --git a/PennMobile/Setup + Navigation/ControllerModel.swift b/PennMobile/Setup + Navigation/ControllerModel.swift index cedcbde28..e5a37755e 100644 --- a/PennMobile/Setup + Navigation/ControllerModel.swift +++ b/PennMobile/Setup + Navigation/ControllerModel.swift @@ -147,23 +147,3 @@ extension ControllerModel { func transition(to feature: Feature, withAnimation: Bool) { } } - -extension ControllerModel { - fileprivate static func isFlingDate() -> Bool { - let beginDateString = "2018-04-13T05:00:00-04:00" - let endDateString = "2018-04-15T05:00:00-04:00" - // standard iso formatter - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" - dateFormatter.locale = Locale(identifier: "en_US_POSIX") - let startDate = dateFormatter.date(from: beginDateString)! - let endDate = dateFormatter.date(from:endDateString)! - // comparison - let today = Date() - return (today > startDate && today < endDate) - } - - static func isReloadNecessary() -> Bool { - return isFlingDate() - } -} diff --git a/PennMobile/Setup + Navigation/TabBarController.swift b/PennMobile/Setup + Navigation/TabBarController.swift index d0b256ba3..40714f7d3 100644 --- a/PennMobile/Setup + Navigation/TabBarController.swift +++ b/PennMobile/Setup + Navigation/TabBarController.swift @@ -12,33 +12,31 @@ final class TabBarController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() - self.navigationController?.navigationBar.isTranslucent = false - self.tabBar.isTranslucent = false - - ControllerModel.shared.viewControllers.forEach { (vc) in - if vc is TabBarShowable { - vc.tabBarItem = (vc as! TabBarShowable).getTabBarItem() - } + + let appearance = UITabBarAppearance() + appearance.configureWithOpaqueBackground() + tabBar.standardAppearance = appearance + + // Required to prevent tab bar's appearance from switching between light and dark mode + if #available(iOS 15.0, *) { + tabBar.scrollEdgeAppearance = appearance } - self.viewControllers = ControllerModel.shared.viewControllers - self.delegate = self + + loadTabs() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } - func reloadTabs() { - let controllerModel = ControllerModel.shared - if (ControllerModel.isReloadNecessary()) { - controllerModel.viewControllers.forEach { (vc) in - if vc is TabBarShowable { - vc.tabBarItem = (vc as! TabBarShowable).getTabBarItem() - } + func loadTabs() { + ControllerModel.shared.viewControllers.forEach { (vc) in + if vc is TabBarShowable { + vc.tabBarItem = (vc as! TabBarShowable).getTabBarItem() } - self.viewControllers = controllerModel.viewControllers - self.delegate = self } + self.viewControllers = ControllerModel.shared.viewControllers + self.delegate = self } } From 4ed9f3f41bfd598594d48744a71f28b26e3269a4 Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Tue, 25 Jan 2022 15:45:19 -0500 Subject: [PATCH 10/21] saftey check of homescreen preload before GSR locations load --- .../Cells/GSR Locations/HomeGSRLocationsCellItem.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift b/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift index 40164acb2..9c156d9ce 100644 --- a/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift +++ b/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift @@ -33,8 +33,12 @@ final class HomeGSRLocationsCellItem: HomeCellItem { task.resume() } else { - let locationSlice = GSRLocationModel.shared.getLocations().shuffle().prefix(upTo: 3) - completion([HomeGSRLocationsCellItem(locations: Array(locationSlice))]) + if GSRLocationModel.shared.getLocations().count > 2 { + let locationSlice = GSRLocationModel.shared.getLocations().shuffle().prefix(upTo: 3) + completion([HomeGSRLocationsCellItem(locations: Array(locationSlice))]) + } else { + completion([]) + } } } } From 8143b0e23c9a562fb37394503fcc021293c9b4bc Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Tue, 25 Jan 2022 15:45:49 -0500 Subject: [PATCH 11/21] removed duplicate setting of tab bar --- PennMobile/Laundry/Controllers/LaundryTableViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/PennMobile/Laundry/Controllers/LaundryTableViewController.swift b/PennMobile/Laundry/Controllers/LaundryTableViewController.swift index 74f757127..a061ef70a 100644 --- a/PennMobile/Laundry/Controllers/LaundryTableViewController.swift +++ b/PennMobile/Laundry/Controllers/LaundryTableViewController.swift @@ -74,7 +74,6 @@ class LaundryTableViewController: GenericTableViewController, IndicatorEnabled, } override func setupNavBar() { - self.tabBarController?.title = "Laundry" tabBarController?.navigationItem.leftBarButtonItem = nil tabBarController?.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(handleEditPressed)) } From b426ccf1f2c9d56ed9330400e09391bfce7f38cb Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Tue, 25 Jan 2022 15:46:07 -0500 Subject: [PATCH 12/21] incremeneted deployment target to ios14 --- PennMobile.xcodeproj/project.pbxproj | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/PennMobile.xcodeproj/project.pbxproj b/PennMobile.xcodeproj/project.pbxproj index a4d62192c..7ae5a3981 100644 --- a/PennMobile.xcodeproj/project.pbxproj +++ b/PennMobile.xcodeproj/project.pbxproj @@ -138,8 +138,8 @@ 66E8ECA72381CB5100945BEA /* TwoFactorTokenGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E8ECA62381CB5100945BEA /* TwoFactorTokenGenerator.swift */; }; 66E8ECA92381CD2F00945BEA /* TOTPNetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E8ECA82381CD2F00945BEA /* TOTPNetworkManager.swift */; }; 6C11C08026F842CF00407C04 /* HomeUpdateCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C11C07F26F842CF00407C04 /* HomeUpdateCellItem.swift */; }; - 6C23AF9526E57903002F60F0 /* BuildFile in Sources */ = {isa = PBXBuildFile; }; - 6C369A1526E39BC100721CA1 /* BuildFile in Sources */ = {isa = PBXBuildFile; }; + 6C23AF9526E57903002F60F0 /* (null) in Sources */ = {isa = PBXBuildFile; }; + 6C369A1526E39BC100721CA1 /* (null) in Sources */ = {isa = PBXBuildFile; }; 6C4CC1FA26E6B1720000B4A8 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 6C4CC1F926E6B1720000B4A8 /* SwiftyJSON */; }; 6C6035F326E722890025FBC7 /* DiningVenueDetailLocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6035ED26E722890025FBC7 /* DiningVenueDetailLocationView.swift */; }; 6C6035F426E722890025FBC7 /* DiningVenueDetailMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6035EE26E722890025FBC7 /* DiningVenueDetailMenuView.swift */; }; @@ -1822,8 +1822,8 @@ F213CCE023C3EE3E000AD90F /* XCRemoteSwiftPackageReference "SwiftSoup" */, F213CCE323C3F240000AD90F /* XCRemoteSwiftPackageReference "Kingfisher" */, F2568A742413534F00561295 /* XCRemoteSwiftPackageReference "SnapKit" */, - 6C4CC1F826E6B1720000B4A8 /* XCRemoteSwiftPackageReference "SwiftyJSON.git" */, - 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk.git" */, + 6C4CC1F826E6B1720000B4A8 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, + 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, ); productRefGroup = 216640611EBADADA00746B8E /* Products */; projectDirPath = ""; @@ -2041,7 +2041,7 @@ 21B8BBB82223963C00EBC1D0 /* Degree.swift in Sources */, 6CAE4390253370E600BD0200 /* DiningVenueView.swift in Sources */, 6CAE4383253370E500BD0200 /* DailyAverageView.swift in Sources */, - 6C369A1526E39BC100721CA1 /* BuildFile in Sources */, + 6C369A1526E39BC100721CA1 /* (null) in Sources */, 2119D26922529A2300693CDB /* HomeGSRLocationsCell.swift in Sources */, 21D5E07623BD45F400B331CC /* KeychainAccessible.swift in Sources */, 21470EBC223DD34200019C10 /* ScheduleEventCell.swift in Sources */, @@ -2145,7 +2145,7 @@ EFE2D6EF239B11050020F6BF /* GroupIndividualSettingView.swift in Sources */, EF6329A22409D2CE00E7ED36 /* GSRGroupConfirmBookingController.swift in Sources */, 21D5E07423BCFE0300B331CC /* CoursePrivacyController.swift in Sources */, - 6C23AF9526E57903002F60F0 /* BuildFile in Sources */, + 6C23AF9526E57903002F60F0 /* (null) in Sources */, B658393B1FABD295009486FC /* LaundryAPIService.swift in Sources */, 6C6035F626E722890025FBC7 /* DiningVenueDetailView.swift in Sources */, 21ABE2A9223D7DEA00199D29 /* ScheduleLayout.swift in Sources */, @@ -2445,7 +2445,7 @@ EXCLUDED_ARCHS = ""; GCC_PREFIX_HEADER = PennMobile/Supporting_Files/PrefixHeader.pch; INFOPLIST_FILE = PennMobile/Supporting_Files/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2478,7 +2478,7 @@ EXCLUDED_ARCHS = ""; GCC_PREFIX_HEADER = PennMobile/Supporting_Files/PrefixHeader.pch; INFOPLIST_FILE = PennMobile/Supporting_Files/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2648,7 +2648,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 6C4CC1F826E6B1720000B4A8 /* XCRemoteSwiftPackageReference "SwiftyJSON.git" */ = { + 6C4CC1F826E6B1720000B4A8 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON.git"; requirement = { @@ -2656,7 +2656,7 @@ minimumVersion = 5.0.1; }; }; - 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk.git" */ = { + 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { @@ -2693,7 +2693,7 @@ /* Begin XCSwiftPackageProductDependency section */ 6C4CC1F926E6B1720000B4A8 /* SwiftyJSON */ = { isa = XCSwiftPackageProductDependency; - package = 6C4CC1F826E6B1720000B4A8 /* XCRemoteSwiftPackageReference "SwiftyJSON.git" */; + package = 6C4CC1F826E6B1720000B4A8 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; productName = SwiftyJSON; }; 6CA1ACDA271D2D5000EDB967 /* Kingfisher */ = { @@ -2703,12 +2703,12 @@ }; 6CE12F8F26E82DC600284D9F /* FirebaseAnalytics */ = { isa = XCSwiftPackageProductDependency; - package = 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk.git" */; + package = 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseAnalytics; }; 6CE12F9126E82DC600284D9F /* FirebaseCrashlytics */ = { isa = XCSwiftPackageProductDependency; - package = 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk.git" */; + package = 6CE12F8E26E82DC600284D9F /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseCrashlytics; }; F213CCE123C3EE3E000AD90F /* SwiftSoup */ = { From c75f58dae31effa568016e088f6dd66485e954ec Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Tue, 25 Jan 2022 15:57:54 -0500 Subject: [PATCH 13/21] fixed dining balance update --- PennMobile/Dining/Networking + Cache/DiningAPI.swift | 4 +--- PennMobile/Login/CampusExpressNetworkManager.swift | 8 +++----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/PennMobile/Dining/Networking + Cache/DiningAPI.swift b/PennMobile/Dining/Networking + Cache/DiningAPI.swift index 5a90d1365..a05dd8f5b 100644 --- a/PennMobile/Dining/Networking + Cache/DiningAPI.swift +++ b/PennMobile/Dining/Networking + Cache/DiningAPI.swift @@ -109,9 +109,7 @@ class DiningAPI: Requestable { // MARK: - Dining Balance API extension DiningAPI { func fetchDiningBalance(_ completion: @escaping (_ diningBalance: DiningBalance?) -> Void) { - CampusExpressNetworkManager.instance.getDiningBalanceHTML { htmlStr, error in - print(htmlStr) - } + CampusExpressNetworkManager.instance.getDiningBalance(completion) } } diff --git a/PennMobile/Login/CampusExpressNetworkManager.swift b/PennMobile/Login/CampusExpressNetworkManager.swift index 24cea1688..454da0571 100644 --- a/PennMobile/Login/CampusExpressNetworkManager.swift +++ b/PennMobile/Login/CampusExpressNetworkManager.swift @@ -55,16 +55,14 @@ extension CampusExpressNetworkManager: PennAuthRequestable { } // TODO: - Finish dining balance fix - func test() { + func getDiningBalance(_ completion: @escaping (_ diningBalance: DiningBalance?) -> Void) { makeAuthRequest(targetUrl: diningUrl, shibbolethUrl: shibbolethUrl) { (data, response, error) in if let data = data, let html = String(data: data, encoding: .utf8) { if let doc = try? SwiftSoup.parse(html), let elementsText = try? doc.getElementsByClass("positive-value").text().split(separator: " "), elementsText.count >= 4 { var diningBalance = elementsText[0] diningBalance.removeFirst() - print(diningBalance) - print(elementsText[1]) - print(elementsText[2]) - print(elementsText[3]) + + completion(DiningBalance(diningDollars: Float(diningBalance) ?? 0, visits: Int(elementsText[1]) ?? 0, guestVisits: Int(elementsText[2]) ?? 0, lastUpdated: Date())) } } else { From 8cf1b5b35e63088c6c69e4b4b6dbacb6849d686f Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Tue, 25 Jan 2022 16:46:24 -0500 Subject: [PATCH 14/21] force fetch dining balance on viewWillAppear --- .../Dining/Controllers/DiningViewController.swift | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/PennMobile/Dining/Controllers/DiningViewController.swift b/PennMobile/Dining/Controllers/DiningViewController.swift index 7dabe1f61..5a9eac007 100755 --- a/PennMobile/Dining/Controllers/DiningViewController.swift +++ b/PennMobile/Dining/Controllers/DiningViewController.swift @@ -34,16 +34,7 @@ class DiningViewController: GenericTableViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) fetchDiningHours() - - if UserDefaults.standard.hasDiningPlan() { - if viewModel.balance == nil { - fetchBalance() - } else { - updateBalanceIfNeeded() - } - } else { - viewModel.balance = DiningBalance(diningDollars: 0, visits: 0, guestVisits: 0, lastUpdated: Date()) - } + fetchBalance() if viewModel.venues[.dining]?.isEmpty ?? true { viewModel.refresh() From 2f6f94f1025ad74cec1bfc2f3976fe7f532f3ec3 Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Tue, 25 Jan 2022 17:32:35 -0500 Subject: [PATCH 15/21] fixed date string formatting in portal post --- PennMobile/General/Extensions.swift | 9 +++++++++ PennMobile/Home/Cells/Post/HomePostCell.swift | 4 +++- PennMobile/Home/Cells/Post/HomePostCellItem.swift | 3 +-- PennMobile/Home/Cells/Post/Post.swift | 4 +++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/PennMobile/General/Extensions.swift b/PennMobile/General/Extensions.swift index 3c7153c24..ea48b9557 100755 --- a/PennMobile/General/Extensions.swift +++ b/PennMobile/General/Extensions.swift @@ -365,6 +365,15 @@ extension DateFormatter { dateFormatter.locale = Locale(identifier: "en_US_POSIX") return dateFormatter } + + static var iso8601Full: DateFormatter { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" + formatter.calendar = Calendar(identifier: .iso8601) + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.locale = Locale(identifier: "en_US_POSIX") + return formatter + } } public extension Collection { diff --git a/PennMobile/Home/Cells/Post/HomePostCell.swift b/PennMobile/Home/Cells/Post/HomePostCell.swift index f4ed652f2..e29aac5cf 100644 --- a/PennMobile/Home/Cells/Post/HomePostCell.swift +++ b/PennMobile/Home/Cells/Post/HomePostCell.swift @@ -106,7 +106,9 @@ extension HomePostCell { self.sourceLabel.text = post.source self.titleLabel.text = post.title self.subtitleLabel?.text = post.subtitle - self.dateLabel.text = post.createdDate + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "MM/dd" + self.dateLabel.text = dateFormatter.string(from: post.createdDate) + " - " + dateFormatter.string(from: post.expireDate) if item.post.source == nil { titleTopConstraintToSource.isActive = false diff --git a/PennMobile/Home/Cells/Post/HomePostCellItem.swift b/PennMobile/Home/Cells/Post/HomePostCellItem.swift index 9288bb1d1..8846d443c 100644 --- a/PennMobile/Home/Cells/Post/HomePostCellItem.swift +++ b/PennMobile/Home/Cells/Post/HomePostCellItem.swift @@ -22,10 +22,9 @@ final class HomePostCellItem: HomeCellItem { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase - decoder.dateDecodingStrategy = .iso8601 + decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full) if let posts = try? decoder.decode([Post].self, from: data) { - print(posts) completion(posts.map({return HomePostCellItem(post: $0)})) } else { completion([]) diff --git a/PennMobile/Home/Cells/Post/Post.swift b/PennMobile/Home/Cells/Post/Post.swift index d8f071177..d687c2ab1 100644 --- a/PennMobile/Home/Cells/Post/Post.swift +++ b/PennMobile/Home/Cells/Post/Post.swift @@ -14,6 +14,8 @@ struct Post: Codable { let subtitle: String? let postUrl: String? let imageUrl: String - let createdDate: String? + let createdDate: Date + let startDate: Date + let expireDate: Date let source: String? } From 15ee9566c7e635f114287f22115da1bf69e7cdc1 Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Tue, 25 Jan 2022 18:31:26 -0500 Subject: [PATCH 16/21] home laundry cell updates successfully --- .../Home/Controllers/HomeViewController.swift | 46 ++++++++++++------- .../LaundryTableViewController.swift | 8 ++-- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/PennMobile/Home/Controllers/HomeViewController.swift b/PennMobile/Home/Controllers/HomeViewController.swift index 4b82470ca..cbe639692 100644 --- a/PennMobile/Home/Controllers/HomeViewController.swift +++ b/PennMobile/Home/Controllers/HomeViewController.swift @@ -235,28 +235,40 @@ extension HomeViewController : DiningCellSettingsDelegate { } } -// MARK: - Laundry Updating +// MARK: - Notification Updating extension HomeViewController { - @objc fileprivate func updateLaundryItemForPreferences(_ sender: Any) { - var preferences = LaundryRoom.getPreferences() - guard let laundryItems = self.tableViewModel.getItems(for: [HomeItemTypes.instance.laundry]) as? [HomeLaundryCellItem] else { return } - var outdatedItems = [HomeLaundryCellItem]() - for item in laundryItems { - if preferences.contains(item.room) { - preferences.remove(at: preferences.firstIndex(of: item.room)!) + @objc fileprivate func updateLaundryItemForPreferences(_ sender: Notification) { + if let laundryRooms = sender.object as? [LaundryRoom] { + if laundryRooms.count == 0 { + if let laundryItem = self.tableViewModel.getItems(for: [HomeItemTypes.instance.laundry]).first { + removeItem(laundryItem) + } } else { - outdatedItems.append(item) + if let laundryItem = self.tableViewModel.getItems(for: [HomeItemTypes.instance.laundry]).first { + (laundryItem as? HomeLaundryCellItem)?.room = laundryRooms[0] + reloadItem(laundryItem) + } else { + tableViewModel.items.append(HomeLaundryCellItem(room: laundryRooms[0])) + self.tableView.reloadData() + } } } - - for i in 0..<(outdatedItems.count) { - if i < preferences.count { - outdatedItems[i].room = preferences[i] - } - } - -// self.reloadItem(<#T##HomeCellItem#>)(for: [HomeItemTypes.instance.laundry]) } + + // TODO: update GSR reservation cell immediately after a booking is made +// @objc fileprivate func addGSRReservation(_ sender: Notification) { +// guard let reservation = sender.object as? GSRReservation else { return } +// +// guard let reservationItem = self.tableViewModel.getItems(for: [HomeItemTypes.instance.reservations]).first as? HomeReservationsCellItem else { +// tableViewModel.items.insert(HomeReservationsCellItem(for: [reservation]), at: 0) +// tableView.reloadData() +// return +// } +// +// reservationItem.reservations.append(reservation) +// reservationItem.reservations = reservationItem.reservations.sorted(by: { $0.start < $1.start }) +// reloadItem(reservationItem) +// } } // MARK: - Register for Notifications diff --git a/PennMobile/Laundry/Controllers/LaundryTableViewController.swift b/PennMobile/Laundry/Controllers/LaundryTableViewController.swift index a061ef70a..d525abec6 100644 --- a/PennMobile/Laundry/Controllers/LaundryTableViewController.swift +++ b/PennMobile/Laundry/Controllers/LaundryTableViewController.swift @@ -196,10 +196,10 @@ extension LaundryTableViewController: RoomSelectionVCDelegate { if let rooms = rooms { self.rooms = rooms self.tableView.reloadData() + self.sendUpdateNotification(for: rooms) } } } - sendUpdateNotification() } } @@ -211,15 +211,15 @@ extension LaundryTableViewController: LaundryCellDelegate { LaundryRoom.setPreferences(for: rooms) UserDBManager.shared.saveLaundryPreferences(for: rooms) tableView.reloadData() - sendUpdateNotification() + sendUpdateNotification(for: rooms) } } } // MARK: - Home Page Notification extension LaundryTableViewController { - fileprivate func sendUpdateNotification() { - NotificationCenter.default.post(name: Notification.Name(rawValue: "LaundryUpdateNotification"), object: nil) + fileprivate func sendUpdateNotification(for rooms: [LaundryRoom]) { + NotificationCenter.default.post(name: Notification.Name(rawValue: "LaundryUpdateNotification"), object: rooms) } } From a0d6ce1429c4f2a5f4bc4fac61a5467fcea5cb4a Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Tue, 25 Jan 2022 18:40:20 -0500 Subject: [PATCH 17/21] hotfix prevent using stored pennkey to login --- PennMobile/Dining/Networking + Cache/DiningAPI.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/PennMobile/Dining/Networking + Cache/DiningAPI.swift b/PennMobile/Dining/Networking + Cache/DiningAPI.swift index a05dd8f5b..e9fc5b19d 100644 --- a/PennMobile/Dining/Networking + Cache/DiningAPI.swift +++ b/PennMobile/Dining/Networking + Cache/DiningAPI.swift @@ -109,7 +109,11 @@ class DiningAPI: Requestable { // MARK: - Dining Balance API extension DiningAPI { func fetchDiningBalance(_ completion: @escaping (_ diningBalance: DiningBalance?) -> Void) { - CampusExpressNetworkManager.instance.getDiningBalance(completion) + if Account.isLoggedIn { + CampusExpressNetworkManager.instance.getDiningBalance(completion) + } else { + completion(DiningBalance(diningDollars: 0, visits: 0, guestVisits: 0, lastUpdated: Date())) + } } } From 5b6de3ab725b1c1191258448c7b92077e016b325 Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Tue, 25 Jan 2022 18:40:53 -0500 Subject: [PATCH 18/21] fix recent gsr location endpoint --- .../Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift b/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift index 9c156d9ce..d684141ce 100644 --- a/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift +++ b/PennMobile/Home/Cells/GSR Locations/HomeGSRLocationsCellItem.swift @@ -17,7 +17,7 @@ final class HomeGSRLocationsCellItem: HomeCellItem { OAuth2NetworkManager.instance.getAccessToken { token in if let token = token { - let request = URLRequest(url: URL(string: "https://pennmobile.com/api/gsr/recent/")!, accessToken: token) + let request = URLRequest(url: URL(string: "https://pennmobile.org/api/gsr/recent/")!, accessToken: token) let task = URLSession.shared.dataTask(with: request) { data, response, error in From 4106fe80b685602c6208606251096a510939b7bd Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Tue, 25 Jan 2022 18:41:31 -0500 Subject: [PATCH 19/21] ensure completion handlers trigger --- PennMobile/Login/CampusExpressNetworkManager.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PennMobile/Login/CampusExpressNetworkManager.swift b/PennMobile/Login/CampusExpressNetworkManager.swift index 454da0571..96550810b 100644 --- a/PennMobile/Login/CampusExpressNetworkManager.swift +++ b/PennMobile/Login/CampusExpressNetworkManager.swift @@ -54,7 +54,6 @@ extension CampusExpressNetworkManager: PennAuthRequestable { } } - // TODO: - Finish dining balance fix func getDiningBalance(_ completion: @escaping (_ diningBalance: DiningBalance?) -> Void) { makeAuthRequest(targetUrl: diningUrl, shibbolethUrl: shibbolethUrl) { (data, response, error) in if let data = data, let html = String(data: data, encoding: .utf8) { @@ -63,9 +62,11 @@ extension CampusExpressNetworkManager: PennAuthRequestable { diningBalance.removeFirst() completion(DiningBalance(diningDollars: Float(diningBalance) ?? 0, visits: Int(elementsText[1]) ?? 0, guestVisits: Int(elementsText[2]) ?? 0, lastUpdated: Date())) + } else { + completion(DiningBalance(diningDollars: 0, visits: 0, guestVisits: 0, lastUpdated: Date())) } } else { - + completion(DiningBalance(diningDollars: 0, visits: 0, guestVisits: 0, lastUpdated: Date())) } } } From 994a19d9e729e632450d916ed84afc1645b5f810 Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Thu, 27 Jan 2022 14:41:09 -0500 Subject: [PATCH 20/21] resolved firebase configuration bug --- PennMobile.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PennMobile.xcodeproj/project.pbxproj b/PennMobile.xcodeproj/project.pbxproj index 7ae5a3981..6b85f05dc 100644 --- a/PennMobile.xcodeproj/project.pbxproj +++ b/PennMobile.xcodeproj/project.pbxproj @@ -140,6 +140,7 @@ 6C11C08026F842CF00407C04 /* HomeUpdateCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C11C07F26F842CF00407C04 /* HomeUpdateCellItem.swift */; }; 6C23AF9526E57903002F60F0 /* (null) in Sources */ = {isa = PBXBuildFile; }; 6C369A1526E39BC100721CA1 /* (null) in Sources */ = {isa = PBXBuildFile; }; + 6C3F194227A32C29007BCB4F /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6C3F194127A32C29007BCB4F /* GoogleService-Info.plist */; }; 6C4CC1FA26E6B1720000B4A8 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 6C4CC1F926E6B1720000B4A8 /* SwiftyJSON */; }; 6C6035F326E722890025FBC7 /* DiningVenueDetailLocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6035ED26E722890025FBC7 /* DiningVenueDetailLocationView.swift */; }; 6C6035F426E722890025FBC7 /* DiningVenueDetailMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6035EE26E722890025FBC7 /* DiningVenueDetailMenuView.swift */; }; @@ -243,7 +244,6 @@ CF29A1771FB788820067D946 /* OnboardingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF29A1671FB7887E0067D946 /* OnboardingController.swift */; }; CF29A1781FB788820067D946 /* PageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF29A1681FB7887E0067D946 /* PageCell.swift */; }; CF7FCA761FAFCD9E0052F0A9 /* RoomSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF7FCA751FAFCD9E0052F0A9 /* RoomSelectionViewController.swift */; }; - E594463B2700EAA2002CF1CA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = E594463A2700EAA1002CF1CA /* GoogleService-Info.plist */; }; E594463D2700EB5D002CF1CA /* AccountPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E594463C2700EB5D002CF1CA /* AccountPageViewController.swift */; }; E5BEF7162704C133009FCDD0 /* ProfilePageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5BEF7152704C133009FCDD0 /* ProfilePageTableViewCell.swift */; }; EF23946423EF4117005BA55F /* GSRGroupInviteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF23946323EF4117005BA55F /* GSRGroupInviteCell.swift */; }; @@ -479,6 +479,7 @@ 66E8ECA62381CB5100945BEA /* TwoFactorTokenGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoFactorTokenGenerator.swift; sourceTree = ""; }; 66E8ECA82381CD2F00945BEA /* TOTPNetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOTPNetworkManager.swift; sourceTree = ""; }; 6C11C07F26F842CF00407C04 /* HomeUpdateCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeUpdateCellItem.swift; sourceTree = ""; }; + 6C3F194127A32C29007BCB4F /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 6C6035ED26E722890025FBC7 /* DiningVenueDetailLocationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiningVenueDetailLocationView.swift; sourceTree = ""; }; 6C6035EE26E722890025FBC7 /* DiningVenueDetailMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiningVenueDetailMenuView.swift; sourceTree = ""; }; 6C6035EF26E722890025FBC7 /* MenuDisclosureGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuDisclosureGroup.swift; sourceTree = ""; }; @@ -582,7 +583,6 @@ CF29A1681FB7887E0067D946 /* PageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageCell.swift; sourceTree = ""; }; CF7FCA751FAFCD9E0052F0A9 /* RoomSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSelectionViewController.swift; sourceTree = ""; }; CFDEBFEB2794599F175AD811 /* Pods_AutomatedScreenshotUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AutomatedScreenshotUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - E594463A2700EAA1002CF1CA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../../Downloads/GoogleService-Info.plist"; sourceTree = ""; }; E594463C2700EB5D002CF1CA /* AccountPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPageViewController.swift; sourceTree = ""; }; E5BEF7152704C133009FCDD0 /* ProfilePageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePageTableViewCell.swift; sourceTree = ""; }; EF23946323EF4117005BA55F /* GSRGroupInviteCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GSRGroupInviteCell.swift; sourceTree = ""; }; @@ -847,7 +847,7 @@ 216640831EBADB7000746B8E /* Supporting_Files */ = { isa = PBXGroup; children = ( - E594463A2700EAA1002CF1CA /* GoogleService-Info.plist */, + 6C3F194127A32C29007BCB4F /* GoogleService-Info.plist */, 2166406F1EBADADA00746B8E /* Info.plist */, 2166406C1EBADADA00746B8E /* LaunchScreen.storyboard */, 216640841EBADB8200746B8E /* PennMobile-Bridging-Header.h */, @@ -1843,7 +1843,7 @@ files = ( 6CAE4392253370E600BD0200 /* sample-dining-venue.json in Resources */, 2166406E1EBADADA00746B8E /* LaunchScreen.storyboard in Resources */, - E594463B2700EAA2002CF1CA /* GoogleService-Info.plist in Resources */, + 6C3F194227A32C29007BCB4F /* GoogleService-Info.plist in Resources */, 6C6035F826E722890025FBC7 /* mock_menu.json in Resources */, 2190FD2F1EBBC8BA00EC683C /* Assets.xcassets in Resources */, ); From 5e6782333444133d1bda9b9075ef7e8b14d4ba97 Mon Sep 17 00:00:00 2001 From: Jong Min Choi <01jongminchoi@gmail.com> Date: Thu, 27 Jan 2022 14:42:48 -0500 Subject: [PATCH 21/21] bump # number and increase deployment target --- PennMobile.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/PennMobile.xcodeproj/project.pbxproj b/PennMobile.xcodeproj/project.pbxproj index 6b85f05dc..133c7314c 100644 --- a/PennMobile.xcodeproj/project.pbxproj +++ b/PennMobile.xcodeproj/project.pbxproj @@ -2315,8 +2315,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CF_BUNDLE_LONG_VERSION_STRING = 6620; - CF_BUNDLE_SHORT_VERSION_STRING = 6.6.2; + CF_BUNDLE_LONG_VERSION_STRING = 6630; + CF_BUNDLE_SHORT_VERSION_STRING = 6.6.3; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -2364,7 +2364,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -2377,8 +2377,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CF_BUNDLE_LONG_VERSION_STRING = 6620; - CF_BUNDLE_SHORT_VERSION_STRING = 6.6.2; + CF_BUNDLE_LONG_VERSION_STRING = 6630; + CF_BUNDLE_SHORT_VERSION_STRING = 6.6.3; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -2420,7 +2420,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule;