Skip to content

Commit

Permalink
App Store release v3.1.0 (#94)
Browse files Browse the repository at this point in the history
* Auto reload schedule in today view

* Move order of setup remote config up

* Readd deleted getSchedule code during merge

* Add reload interval values to remote config

* Reformat code

* Fixed  bug of schedule failing to update itself

* Clean up warnings

* fixed #93

* Fixed #92; Period 8 incorrectly show on holidays
  • Loading branch information
jevonmao authored Apr 23, 2022
1 parent b48acac commit c58f8ac
Show file tree
Hide file tree
Showing 18 changed files with 196 additions and 107 deletions.
122 changes: 60 additions & 62 deletions SMHS.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Sources/SMHS (iOS)/AppCycle/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// Configure Firebase Suite
FirebaseApp.configure()
setupFirebaseRemoteConfig()
setupPushNotifications()
setupFirebaseMessaging()
setupFirebaseRemoteConfig()


return true
}

Expand Down Expand Up @@ -53,7 +53,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions)
-> Void) {
// Change this to your preferred presentation option
completionHandler([[.alert, .banner, .sound]])
completionHandler([.banner, .sound, .list])
}

// Receive notification for when app is in foreground
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ struct TodayHeroView: View {

struct TodayHeroView_Previews: PreviewProvider {
static var previews: some View {
TodayView(networkLoadViewModel: NetworkLoadViewModel(dataReload: {_,_ in}), scheduleViewViewModel: .mockScheduleView, todayViewViewModel: .mockViewModel)
TodayView(networkLoadViewModel: NetworkLoadViewModel(dataReload: {_,_,_ in}), scheduleViewViewModel: .mockScheduleView, todayViewViewModel: .mockViewModel)
.environmentObject(UserSettings())
}
}
3 changes: 2 additions & 1 deletion Sources/SMHS (iOS)/Views/TodayView/TodayView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ struct TodayView: View {
UISegmentedControl.appearance().selectedSegmentTintColor = UIColor(appPrimary)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor(appSecondary)], for: .normal)
scheduleViewViewModel.reloadData()
}
.onDisappear {
todayViewViewModel.showNetworkError = true
Expand Down Expand Up @@ -96,7 +97,7 @@ struct TodayViewHeader: View {

struct TodayView_Previews: PreviewProvider {
static var previews: some View {
TodayView(networkLoadViewModel: NetworkLoadViewModel(dataReload: {_,_ in }),
TodayView(networkLoadViewModel: NetworkLoadViewModel(dataReload: {_,_,_ in }),
scheduleViewViewModel: SharedScheduleInformation())
.environmentObject(UserSettings())
}
Expand Down
24 changes: 24 additions & 0 deletions Sources/Shared/Models/Endpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ extension Endpoint {
request.httpBody = requestBody.percentEncoded()
}
}
let userAgent = AppVersionStatus.appDisplayName
+ "/"
+ AppVersionStatus.currentVersion!
+ " "
+ "Developer email/[email protected]"
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
request.setValue("br;q=1.0, gzip;q=0.9, deflate;q=0.8", forHTTPHeaderField: "Accept-Encoding")
if isApplicationJson {
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
Expand All @@ -62,6 +68,9 @@ extension Endpoint {
static let AERIES_API_LOGIN_PATH = "/parent/LoginParent.aspx"
static let AERIES_API_ALT_GRADES_PATH = "/Parent/Widgets/ClassSummary/GetClassSummary"

static let APPSERV_API_HOST = "appserv.u360mobile.com"
static let APPSERV_API_SCHEDULE_PATH = "/354/calendarfeed.php"

static func studentLogin(email: String,
password: String,
debugMode: Bool = false) -> Endpoint {
Expand Down Expand Up @@ -148,5 +157,20 @@ extension Endpoint {
value: formatter.serverTimeFormat(date))],
httpMethod: "GET")
}

static func getSchedule(date: Date) -> Endpoint {
let formatter = DateFormatter()
return Endpoint(host: APPSERV_API_HOST,
path: APPSERV_API_SCHEDULE_PATH,
queryItems: [.init(name: "i", value: "santamargaritahs"),
.init(name: "pageSize", value: "25"),
.init(name: "pageNumber", value: "1"),
.init(name: "dateStart", value: formatter.yearMonthDayFormat(date)),
.init(name: "categoryId", value: "0"),
.init(name: "tz", value: "America%2FLos_Angeles"),
.init(name: "mid", value: "1422"),
.init(name: "smid", value: "46492")],
httpMethod: "GET")
}

}
27 changes: 27 additions & 0 deletions Sources/Shared/Models/GlobalObjects.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,31 @@ extension RemoteConfig {

return url
}

var RELOAD_INTERVAL_GRADE: TimeInterval {
#if DEBUG
0
#else
globalRemoteConfig.configValue(forKey: "reload_interval_grade").numberValue.doubleValue
#endif
}

var RELOAD_INTERVAL_SCHEDULE: TimeInterval {
#if DEBUG
0
#else
globalRemoteConfig.configValue(forKey: "reload_interval_schedule").numberValue.doubleValue
#endif
}

var NO_SCHOOL_IDENTIFIERS: [String] {
let data = globalRemoteConfig.configValue(forKey: "no_school_identifier").jsonValue
if let values = (data as? [String: [String]]),
let identifiers = values["contains"] {
return identifiers
}
else {
return ["SMCHS Events", "Holiday"]
}
}
}
4 changes: 2 additions & 2 deletions Sources/Shared/Models/NetworkLoadViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class NetworkLoadViewModel: ObservableObject {
lastStatus == .satisfied
}

typealias Reloader = (Date, @escaping (Bool) -> Void) -> Void
typealias Reloader = (Date, Bool, @escaping (Bool) -> Void) -> Void
var dataReload: Reloader

init(dataReload: @escaping Reloader) {
Expand All @@ -34,7 +34,7 @@ final class NetworkLoadViewModel: ObservableObject {
func reloadDataNow() {
//Show indicator while loading
isLoading = true
dataReload(Date()) {success in
dataReload(Date(), true) {success in
DispatchQueue.main.asyncAfter(deadline: .now()+0.2) {
self.isLoading = false
}
Expand Down
40 changes: 24 additions & 16 deletions Sources/Shared/Models/SharedScheduleInformation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ final class SharedScheduleInformation: ObservableObject {
self.urlString = urlString
self.downloader = downloader
print("Called fetch data from initializer...")
fetchData()
fetchData(purgeExisting: true)

let shouldPurge = globalRemoteConfig.configValue(forKey: "purge_data_onupdate").boolValue
// Purge all data when app update applied
Expand All @@ -69,17 +69,16 @@ final class SharedScheduleInformation: ObservableObject {
func reloadData() {
if let time = lastReloadTime {
// minimum reload interval is 6 hours
if abs(Date().timeIntervalSince(time)) > TimeInterval(21600) {
if abs(Date().timeIntervalSince(time)) > TimeInterval(globalRemoteConfig.RELOAD_INTERVAL_SCHEDULE) {
print("Reload valid, fetching data")
fetchData()
fetchData(purgeExisting: true)
lastReloadTime = Date()
}
print("Reload invalid")
}
else {
print("Reload 1st time, fetching data")
lastReloadTime = Date()
fetchData()
fetchData(purgeExisting: true)
}

}
Expand All @@ -93,6 +92,7 @@ final class SharedScheduleInformation: ObservableObject {
// 2. smhs.org ICS calendar feed, used as a
// redundency to fallback on if above fails
func fetchData(startDate: Date = Date().startOfWeek(),
purgeExisting: Bool = false,
completion: ((Bool) -> Void)? = nil) {
// AppServ API
let endpoint = Endpoint.getSchedule(date: startDate)
Expand All @@ -102,19 +102,26 @@ final class SharedScheduleInformation: ObservableObject {
if let data = response.data {
let xml = XML.parse(data)

var scheduleDays = [(String, String)]()
var scheduleDays = [(date: String, schedule: String, title: String)]()
for day in xml["CALENDAR", "EVENT"] {
if let scheduleText = day["DESCRIPTION"].text,
let date = day["EVENTDATE"].text {
scheduleDays.append((date, scheduleText))
let date = day["EVENTDATE"].text,
let title = day["TITLE"].text {
scheduleDays.append((date, scheduleText, title))
}
}
let formatter = DateFormatter()
let metadata = xml["CALENDAR", "METADATA"]
self?.minDate = formatter.serverTimeFormat(metadata["MINDATE"].text)
self?.maxDate = formatter.serverTimeFormat(metadata["MAXDATE"].text)
let fetchedSchedule = self?.dateHelper.parseScheduleXML(forDays: scheduleDays)
self?.scheduleWeeks.appendUnion(contentsOf: fetchedSchedule)
if purgeExisting {
self?.scheduleWeeks = fetchedSchedule ?? []
}
else {
self?.scheduleWeeks.appendUnion(contentsOf: fetchedSchedule)
}

self?.isLoading = false
#if DEBUG
debugPrint("✅ Successfully fetched from main AppServ API.")
Expand Down Expand Up @@ -186,23 +193,24 @@ final class SharedScheduleInformation: ObservableObject {
ICSText = nil; scheduleWeeks = [];
}

// Reloads the infinite scroll as needed
func reloadScrollList(currentWeek: ScheduleWeek) {
if currentWeek == scheduleWeeks.last {
guard let lastDay = scheduleWeeks.last?.scheduleDays.last?.date
guard let lastDay = scheduleWeeks.last?.scheduleDays.last?.date,
let minDate = minDate,
let maxDate = maxDate
else {
return
}

guard let minDate = minDate, let maxDate = maxDate
else {
if (lastDay < maxDate && lastDay > minDate)
&& scheduleWeeks.isNotLast(for: currentWeek){
debugPrint("✅ Reloaded infinite scroll list successfully")
fetchData(startDate: lastDay)
return
}

if lastDay < maxDate && lastDay > minDate {
fetchData(startDate: lastDay)
}
}
debugPrint("⚠️ reloadScrollList() called, but did not realod.")
}
}

Expand Down
7 changes: 7 additions & 0 deletions Sources/Shared/Utility/Extensions/Array.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,11 @@ extension Array {
guard let newElements = newElements else {return}
self.append(contentsOf: newElements.filter {!self.contains($0)})
}

func isNotLast(for element: Self.Element) -> Bool where Element: Equatable {
if let index = self.firstIndex(of: element) {
return index < self.count - 1
}
return true
}
}
9 changes: 5 additions & 4 deletions Sources/Shared/Utility/Extensions/Date.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
import Foundation

extension Date {

static func currentWeekday(for date: Date = Date()) -> Int {Calendar.current.component(.weekday, from: date)-1}

static func getDayOfTheWeek(for date: Date = Date()) -> Int {Calendar.iso8601.component(.weekday, from: date)-1}

// 0 is Sunday, week starts on Monday (1), end on Saturday (6)
static func getDayOfTheWeek(for date: Date = Date()) -> Int {
Calendar.iso8601.component(.weekday, from: date) - 1
}

func isBetween(_ date1: Date, and date2: Date) -> Bool {(min(date1, date2) ... max(date1, date2)).contains(self)}

Expand Down
2 changes: 0 additions & 2 deletions Sources/Shared/Utility/Keychain/KeychainAccessibility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,7 @@ private let keychainItemAccessibilityLookup: [KeychainItemAccessibility:CFString
var lookup: [KeychainItemAccessibility:CFString] = [
.afterFirstUnlock: kSecAttrAccessibleAfterFirstUnlock,
.afterFirstUnlockThisDeviceOnly: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
.always: kSecAttrAccessibleAlways,
.whenPasscodeSetThisDeviceOnly: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
.alwaysThisDeviceOnly : kSecAttrAccessibleAlwaysThisDeviceOnly,
.whenUnlocked: kSecAttrAccessibleWhenUnlocked,
.whenUnlockedThisDeviceOnly: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,7 @@ class JSONNull: Codable, Hashable {
return true
}

public var hashValue: Int {
return 0
}
func hash(into hasher: inout Hasher) {}

public init() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,11 @@ class GradesDetailViewModel: ObservableObject {
}

func computeOverallPercentage(with gradesRubric: [GradesRubricRawResponse.Category]) -> Double {
var totalGrade = 0.0
var totalWeight = 0.0
var totalGrade: Double = 0.0
var totalWeight: Double = 0.0
for category in gradesRubric {
guard category.isDoingWeight else { continue }
let correctScore = self.detailedAssignments
let correctScore: Double = self.detailedAssignments
.filter {$0.category == category.category}
.filter {$0.dateCompleted != nil}
.filter {
Expand All @@ -144,7 +144,7 @@ class GradesDetailViewModel: ObservableObject {
.map {$0.numberCorrect}
.reduce(0, {$0 + $1})

let possibleScore = self.detailedAssignments
let possibleScore: Double = self.detailedAssignments
.filter {$0.category == category.category}
.filter {$0.dateCompleted != nil}
.filter {
Expand All @@ -157,13 +157,13 @@ class GradesDetailViewModel: ObservableObject {
.reduce(0, {$0 + $1})

//guard let _assignments = assignments else { return }
let weight = Double(category.percentOfGrade) / 100
let weight: Double = Double(category.percentOfGrade) / 100
if possibleScore > 0 {
totalGrade += (correctScore / possibleScore) * weight
totalGrade += (correctScore / possibleScore) * weight
totalWeight += weight
}
}
let percent = (totalGrade / totalWeight) * 100
let percent: Double = (totalGrade / totalWeight) * 100
return percent.truncate(places: 2)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ extension GradesViewModel {
loginAndFetch()
#else
if let time = lastReloadTime {
if abs(Date().timeIntervalSince(time)) > TimeInterval(60 * 10) {
if abs(Date().timeIntervalSince(time)) > TimeInterval(globalRemoteConfig.RELOAD_INTERVAL_GRADE) {
loginAndFetch()
lastReloadTime = Date()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ extension ScheduleDay {
guard p8DayIntArray.contains(Double(self.dayOfTheWeek))
else { return periods }

guard !globalRemoteConfig.NO_SCHOOL_IDENTIFIERS.contains(dayTitle ?? "")
else {return periods}

//Get periods's start and end times from remote config
let timesConfig = globalRemoteConfig.configValue(forKey: "period_eight_time").jsonValue
guard let times = timesConfig as? [String: String]
Expand Down
Loading

0 comments on commit c58f8ac

Please sign in to comment.