Archived
Added testing menu, made layout changes, optimised DataFetcher.swift, removed Alamofire dependency
This commit is contained in:
@@ -176,8 +176,10 @@
|
||||
</segmentedControl>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" horizontalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="detailDisclosure" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="d3J-cU-Fkz">
|
||||
<rect key="frame" x="365.66666666666669" y="8" width="24.333333333333314" height="28"/>
|
||||
<gestureRecognizers/>
|
||||
<connections>
|
||||
<action selector="orderHelpButton:" destination="5GD-rn-jaq" eventType="touchUpInside" id="0eZ-qg-y9V"/>
|
||||
<outletCollection property="gestureRecognizers" destination="nv1-BD-WsC" appends="YES" id="Ldx-dh-Epl"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
@@ -240,6 +242,7 @@
|
||||
</switch>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Auto refresh on app start" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oiQ-2o-sH8">
|
||||
<rect key="frame" x="16" y="8" width="317" height="28"/>
|
||||
<gestureRecognizers/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -306,6 +309,11 @@
|
||||
<action selector="infoLongPressListener:" destination="5GD-rn-jaq" id="kR0-b7-jg0"/>
|
||||
</connections>
|
||||
</pongPressGestureRecognizer>
|
||||
<pongPressGestureRecognizer allowableMovement="10" minimumPressDuration="0.5" id="nv1-BD-WsC">
|
||||
<connections>
|
||||
<action selector="openHiddenInputDialog:" destination="5GD-rn-jaq" id="9JK-zc-scO"/>
|
||||
</connections>
|
||||
</pongPressGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3471" y="-1221"/>
|
||||
</scene>
|
||||
@@ -370,7 +378,7 @@
|
||||
</items>
|
||||
</navigationBar>
|
||||
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="osl-6A-DdT">
|
||||
<rect key="frame" x="0.0" y="88" width="414" height="774"/>
|
||||
<rect key="frame" x="0.0" y="88" width="414" height="808"/>
|
||||
<color key="backgroundColor" name="colorBackground"/>
|
||||
<collectionViewLayout key="collectionViewLayout" id="Rj8-fR-cpM" customClass="MagazineLayout" customModule="MagazineLayout"/>
|
||||
<cells/>
|
||||
@@ -380,7 +388,7 @@
|
||||
<constraints>
|
||||
<constraint firstItem="pyq-c3-PYx" firstAttribute="top" secondItem="hyb-lI-39K" secondAttribute="top" id="ICp-uJ-Gf8"/>
|
||||
<constraint firstAttribute="trailing" secondItem="osl-6A-DdT" secondAttribute="trailing" id="VbM-jY-jbB"/>
|
||||
<constraint firstItem="hyb-lI-39K" firstAttribute="bottom" secondItem="osl-6A-DdT" secondAttribute="bottom" id="XTp-jd-vxs"/>
|
||||
<constraint firstAttribute="bottom" secondItem="osl-6A-DdT" secondAttribute="bottom" id="XTp-jd-vxs"/>
|
||||
<constraint firstItem="hyb-lI-39K" firstAttribute="trailing" secondItem="pyq-c3-PYx" secondAttribute="trailing" id="dZt-Hu-jad"/>
|
||||
<constraint firstItem="osl-6A-DdT" firstAttribute="leading" secondItem="hyb-lI-39K" secondAttribute="leading" id="hHO-M8-dy2"/>
|
||||
<constraint firstItem="osl-6A-DdT" firstAttribute="top" secondItem="pyq-c3-PYx" secondAttribute="bottom" id="jd6-3S-R8v"/>
|
||||
@@ -418,7 +426,7 @@
|
||||
</items>
|
||||
</navigationBar>
|
||||
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="PuQ-HH-9DW">
|
||||
<rect key="frame" x="0.0" y="88" width="414" height="774"/>
|
||||
<rect key="frame" x="0.0" y="88" width="414" height="808"/>
|
||||
<color key="backgroundColor" name="colorBackground"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="U1s-L9-Ptw" customClass="MagazineLayout" customModule="MagazineLayout">
|
||||
<size key="itemSize" width="50" height="50"/>
|
||||
@@ -440,7 +448,7 @@
|
||||
<constraint firstItem="hA8-ki-X8m" firstAttribute="trailing" secondItem="sxo-IA-DXC" secondAttribute="trailing" id="eLY-V4-1lX"/>
|
||||
<constraint firstItem="sxo-IA-DXC" firstAttribute="trailing" secondItem="PuQ-HH-9DW" secondAttribute="trailing" id="iAf-Pv-Hl8"/>
|
||||
<constraint firstItem="PuQ-HH-9DW" firstAttribute="top" secondItem="hA8-ki-X8m" secondAttribute="bottom" id="lNF-lt-wSq"/>
|
||||
<constraint firstItem="sxo-IA-DXC" firstAttribute="bottom" secondItem="PuQ-HH-9DW" secondAttribute="bottom" id="m44-uV-U72"/>
|
||||
<constraint firstAttribute="bottom" secondItem="PuQ-HH-9DW" secondAttribute="bottom" id="m44-uV-U72"/>
|
||||
<constraint firstItem="hA8-ki-X8m" firstAttribute="leading" secondItem="sxo-IA-DXC" secondAttribute="leading" id="vWh-ct-B5X"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="sxo-IA-DXC"/>
|
||||
@@ -807,7 +815,7 @@ Tell me your name.</string>
|
||||
<subviews>
|
||||
<wkWebView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xo2-qg-VPG">
|
||||
<rect key="frame" x="0.0" y="140" width="414" height="722"/>
|
||||
<color key="backgroundColor" red="0.36078431370000003" green="0.38823529410000002" blue="0.4039215686" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="backgroundColor" name="colorBackground"/>
|
||||
<wkWebViewConfiguration key="configuration" allowsAirPlayForMediaPlayback="NO" allowsPictureInPictureMediaPlayback="NO">
|
||||
<dataDetectorTypes key="dataDetectorTypes" none="YES"/>
|
||||
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
|
||||
|
||||
@@ -40,6 +40,10 @@ class ColourPickerView: UIViewController, UICollectionViewDataSource, UICollecti
|
||||
self.toolbar!.topItem!.title = "\(course)"
|
||||
self.collectionView.delegate = self
|
||||
self.collectionView.dataSource = self
|
||||
|
||||
if !self.df.hasHomeButton() {
|
||||
self.collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 34, right: 0)
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
|
||||
@@ -36,6 +36,11 @@ class CoursePickerViewController: UIViewController, UICollectionViewDataSource,
|
||||
|
||||
self.collectionView.delegate = self
|
||||
self.collectionView.dataSource = self
|
||||
|
||||
if !self.df.hasHomeButton() {
|
||||
self.collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 34, right: 0)
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Crashlytics
|
||||
import Toast_Swift
|
||||
|
||||
class CustomizationViewController: UITableViewController, UITextFieldDelegate, UIAdaptivePresentationControllerDelegate {
|
||||
|
||||
@@ -81,6 +83,53 @@ class CustomizationViewController: UITableViewController, UITextFieldDelegate, U
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func openHiddenInputDialog(_ sender: UILongPressGestureRecognizer) {
|
||||
sender.minimumPressDuration = 2
|
||||
if sender.state == .ended {
|
||||
let alert = UIAlertController(title: NSLocalizedString("hidden_dialog_title", comment: ""), message: NSLocalizedString("hidden_dialog_desc", comment: ""), preferredStyle: .alert)
|
||||
alert.addTextField { textField in }
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("apply", comment: ""), style: .default, handler: { [weak alert] (action) -> Void in
|
||||
let toastMessage: String
|
||||
switch alert!.textFields![0].text {
|
||||
case "_FIRSTTIME":
|
||||
self.prefs.set(false, forKey: "setup_finished")
|
||||
toastMessage = "First time flag cleared"
|
||||
break
|
||||
case "_LOGIN":
|
||||
self.prefs.set(false, forKey: "logged_in")
|
||||
toastMessage = "Login flag cleared"
|
||||
break
|
||||
case "_DEVCHANNEL":
|
||||
let newValue = !self.prefs.bool(forKey: "subscribed_to_dev_channel")
|
||||
self.prefs.set(newValue, forKey: "subscribed_to_dev_channel")
|
||||
let sub: String
|
||||
if newValue {
|
||||
sub = "Subscribed to"
|
||||
} else {
|
||||
sub = "Unsubscribed from"
|
||||
}
|
||||
toastMessage = "\(sub) Firebase development channel"
|
||||
break
|
||||
case "_CRASH":
|
||||
toastMessage = ""
|
||||
Crashlytics.sharedInstance().crash()
|
||||
break
|
||||
case "_TESTURLS":
|
||||
let newValue = !self.prefs.bool(forKey: "use_test_urls")
|
||||
self.prefs.set(newValue, forKey: "use_test_urls")
|
||||
toastMessage = "Test URLs set to \(newValue)"
|
||||
break
|
||||
default:
|
||||
toastMessage = NSLocalizedString("invalid_code", comment: "")
|
||||
break
|
||||
}
|
||||
self.view.makeToast(toastMessage, position: .center)
|
||||
}))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
||||
+57
-40
@@ -8,7 +8,6 @@
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
import Alamofire
|
||||
import SwiftSoup
|
||||
import SQLite
|
||||
|
||||
@@ -46,23 +45,14 @@ class DataFetcher {
|
||||
|
||||
let text = Expression<String>("text")
|
||||
|
||||
func doAsync(do task: String, completionHandler: @escaping (_ substitutions: Any) -> ()) {
|
||||
func doAsync(do task: String, completionHandler: @escaping () -> ()) {
|
||||
DispatchQueue(label: "work-queue").async {
|
||||
let foodUrl = "https://djd4rkn355.github.io/food.html"
|
||||
let url = "https://djd4rkn355.github.io/avh_substitutions.html"
|
||||
Alamofire.request(url).responseString { response in
|
||||
if let html = response.result.value {
|
||||
Alamofire.request(foodUrl).responseString { response in
|
||||
if let foodHtml = response.result.value {
|
||||
completionHandler(self.parseHTML(html: html, food: foodHtml, type: task))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.fetchData(type: task)
|
||||
completionHandler()
|
||||
}
|
||||
}
|
||||
|
||||
private func createAndClearTables(in db: Connection) -> Bool {
|
||||
private func createTables(in db: Connection) -> Bool {
|
||||
do {
|
||||
try db.run(substitutions.create(ifNotExists: true) { t in
|
||||
t.column(id, primaryKey: true)
|
||||
@@ -99,16 +89,21 @@ class DataFetcher {
|
||||
t.column(text)
|
||||
})
|
||||
|
||||
try db.run(substitutions.delete())
|
||||
try db.run(personal.delete())
|
||||
try db.run(foodmenu.delete())
|
||||
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func clearDatabase(in db: Connection) {
|
||||
do {
|
||||
try db.run(substitutions.delete())
|
||||
try db.run(personal.delete())
|
||||
try db.run(foodmenu.delete())
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
private func insert(into table: Table, substitution s: SubstModel) -> Insert {
|
||||
|
||||
return table.insert(group <- s.group, course <- s.course, additional <- s.additional, date <- s.date, time <- s.time, room <- s.room, teacher <- s.teacher, subst_type <- s.type, group_priority <- Int64(s.groupPriority), date_priority <- Int64(s.datePriority), website_priority <- Int64(s.websitePriority))
|
||||
@@ -163,19 +158,30 @@ class DataFetcher {
|
||||
return Int(String(d[d.index(periodIndex ?? d.startIndex, offsetBy: 1)...d.index(d.startIndex, offsetBy: d.count - 1)]))
|
||||
}
|
||||
|
||||
private func parseHTML(html: String, food: String, type: String) -> Any {
|
||||
private func fetchData(type: String) {
|
||||
var substitutionUrl = "https://djd4rkn355.github.io/avh_substitutions.html"
|
||||
var foodUrl = "https://djd4rkn355.github.io/food.html"
|
||||
|
||||
var infoString = ""
|
||||
if self.prefs.bool(forKey: "use_test_urls") {
|
||||
substitutionUrl = "https://djd4rkn355.github.io/subst_test.html"
|
||||
foodUrl = "https://djd4rkn355.github.io/food_test.html"
|
||||
}
|
||||
|
||||
var isPersonalEmpty = true
|
||||
var personalPlanCount = 0
|
||||
var mWebsitePriority = 0
|
||||
|
||||
var substitutionList = [SubstModel]()
|
||||
var personalList = [SubstModel]()
|
||||
var infoString = ""
|
||||
var foodmenuList = [String]()
|
||||
|
||||
do {
|
||||
let db = try Connection("\(path)/db.sqlite3")
|
||||
_ = self.createAndClearTables(in: db)
|
||||
_ = self.createTables(in: db)
|
||||
|
||||
let substitutionsDocument = try SwiftSoup.parse(html)
|
||||
let substitutionContents = try String(contentsOf: URL(string: substitutionUrl)!, encoding: .utf8)
|
||||
let substitutionsDocument = try SwiftSoup.parse(substitutionContents)
|
||||
let rows = try substitutionsDocument.select("tr")
|
||||
|
||||
let groupPreference = prefs.string(forKey: "classes")
|
||||
@@ -205,10 +211,10 @@ class DataFetcher {
|
||||
|
||||
mWebsitePriority += 1
|
||||
|
||||
try db.run(self.insert(into: substitutions, substitution: substitution))
|
||||
substitutionList.append(substitution)
|
||||
|
||||
if self.isPersonal(with: groupPreference ?? "", and: coursePreference ?? "", for: substitution) || isPSA {
|
||||
try db.run(self.insert(into: personal, substitution: substitution))
|
||||
personalList.append(substitution)
|
||||
if !isPSA {
|
||||
isPersonalEmpty = false
|
||||
personalPlanCount += 1
|
||||
@@ -224,7 +230,7 @@ class DataFetcher {
|
||||
|
||||
if isPersonalEmpty {
|
||||
let emptySubstitution = SubstModel(group: "", course: NSLocalizedString("personal_plan_empty", comment: ""), additional: "", date: "", time: "", room: "", teacher: "", type: "", groupPriority: 0, datePriority: 0, websitePriority: 0)
|
||||
try db.run(self.insert(into: personal, substitution: emptySubstitution))
|
||||
personalList.append(emptySubstitution)
|
||||
}
|
||||
|
||||
// start info fetch
|
||||
@@ -238,11 +244,10 @@ class DataFetcher {
|
||||
infoString += "\n\n\(try item.text())"
|
||||
}
|
||||
|
||||
prefs.set(infoString.trimmingCharacters(in: .whitespacesAndNewlines), forKey: "information")
|
||||
|
||||
// start food fetch
|
||||
|
||||
let foodDocument = try SwiftSoup.parse(food)
|
||||
let foodContents = try String(contentsOf: URL(string: foodUrl)!, encoding: .utf8)
|
||||
let foodDocument = try SwiftSoup.parse(foodContents)
|
||||
let foodItems = try foodDocument.select("th")
|
||||
|
||||
var indices = [Int]()
|
||||
@@ -263,22 +268,27 @@ class DataFetcher {
|
||||
s += "\n\(try foodItems.get(i2).text())"
|
||||
}
|
||||
}
|
||||
try db.run(foodmenu.insert(text <- s))
|
||||
foodmenuList.append(s)
|
||||
}
|
||||
|
||||
self.clearDatabase(in: db)
|
||||
|
||||
for subst in substitutionList {
|
||||
try db.run(self.insert(into: substitutions, substitution: subst))
|
||||
}
|
||||
|
||||
for subst in personalList {
|
||||
try db.run(self.insert(into: personal, substitution: subst))
|
||||
}
|
||||
|
||||
prefs.set(infoString.trimmingCharacters(in: .whitespacesAndNewlines), forKey: "information")
|
||||
|
||||
for food in foodmenuList {
|
||||
try db.run(foodmenu.insert(text <- food))
|
||||
}
|
||||
|
||||
} catch {
|
||||
}
|
||||
|
||||
switch type {
|
||||
case "plan":
|
||||
return self.getSubstitutionsFromDatabase()
|
||||
case "personal":
|
||||
return self.getPersonalSubstitutionsFromDatabase()
|
||||
case "info":
|
||||
return infoString
|
||||
default:
|
||||
return self.readMenu()
|
||||
}
|
||||
}
|
||||
|
||||
private func checkStringForArray(s: String, checking: [String], lowercase: Bool) -> Bool {
|
||||
@@ -518,4 +528,11 @@ class DataFetcher {
|
||||
})
|
||||
return alert
|
||||
}
|
||||
|
||||
func hasHomeButton() -> Bool {
|
||||
if #available(iOS 11.0, *), let keyWindow = UIApplication.shared.keyWindow, keyWindow.safeAreaInsets.bottom > 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,15 +62,16 @@ class MenuViewController : UICollectionViewController, UICollectionViewDelegateM
|
||||
}
|
||||
|
||||
@objc private func objDoAsync(_ sender: Any) {
|
||||
df.doAsync(do: "menu") { menuItems in
|
||||
self.items = menuItems as! [String]
|
||||
|
||||
df.doAsync(do: "menu") {
|
||||
DispatchQueue.main.async {
|
||||
self.items = self.df.readMenu()
|
||||
// UIView.performWithoutAnimation {
|
||||
self.collectionView.reloadData()
|
||||
// }
|
||||
|
||||
self.refreshControl.endRefreshing()
|
||||
self.df.setTabBarBadge(for: self.tabBarController?.tabBar.items)
|
||||
|
||||
self.refreshControl.endRefreshing()
|
||||
self.df.setTabBarBadge(for: self.tabBarController?.tabBar.items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,12 @@ class PlanViewController : UICollectionViewController, UICollectionViewDelegateM
|
||||
if self.prefs.bool(forKey: "logged_in") {
|
||||
Messaging.messaging().subscribe(toTopic: "substitutions-ios")
|
||||
Messaging.messaging().subscribe(toTopic: "substitutions-broadcast")
|
||||
Crashlytics.sharedInstance().setUserIdentifier(UserDefaults.standard.string(forKey: "username"))
|
||||
// TODO: check if these can be put in AppDelegate
|
||||
|
||||
if self.prefs.bool(forKey: "subscribed_to_dev_channel") {
|
||||
Messaging.messaging().subscribe(toTopic: "substitutions-debug")
|
||||
} else {
|
||||
Messaging.messaging().unsubscribe(fromTopic: "substitutions-debug")
|
||||
}
|
||||
}
|
||||
|
||||
view.addSubview(collectionView)
|
||||
@@ -112,13 +116,15 @@ class PlanViewController : UICollectionViewController, UICollectionViewDelegateM
|
||||
}
|
||||
|
||||
@objc private func objDoAsync(_ sender: Any) {
|
||||
self.df.doAsync(do: self.getViewType()) { substitutions in
|
||||
self.substs = substitutions as! [SubstModel]
|
||||
// UIView.performWithoutAnimation {
|
||||
self.collectionView.reloadData()
|
||||
// }
|
||||
self.refreshControl.endRefreshing()
|
||||
self.df.setTabBarBadge(for: self.tabBarController?.tabBar.items)
|
||||
self.df.doAsync(do: self.getViewType()) {
|
||||
DispatchQueue.main.async {
|
||||
self.substs = self.getFromDatabase()
|
||||
// UIView.performWithoutAnimation {
|
||||
self.collectionView.reloadData()
|
||||
// }
|
||||
self.refreshControl.endRefreshing()
|
||||
self.df.setTabBarBadge(for: self.tabBarController?.tabBar.items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,3 +85,8 @@
|
||||
"enter_order_help" = "Du kannst zwischen zwei Sortierweisen auswählen, welche die Reihenfolge der Einträge auf dem Vertretungsplan bestimmen.\n\nStandardmäßig ist \'App-Sortierung\' ausgewählt; dies bietet einen konsistenten Plan sortiert nach Klassen, dann nach Stunden.\n\n\'Website-Sortierung\' bietet die Original-Reihenfolge der Website. Diese ist jedoch nicht konsistent, weswegen \'App-Sortierung\' standardmäßig ausgewählt ist.";
|
||||
"made_by_deniz" = "Entwickelt von Deniz :)";
|
||||
"customize_course_colors" = "Kursfarben einstellen";
|
||||
|
||||
"hidden_dialog_title" = "Testmenü";
|
||||
"hidden_dialog_desc" = "Dieses Menü dient für Tests.";
|
||||
"invalid_code" = "Ungültiger Code";
|
||||
"apply" = "Anwenden";
|
||||
|
||||
@@ -86,3 +86,8 @@
|
||||
"enter_order_help" = "You may pick between two ordering systems that dictate the order in which the items on the substitution plan will appear in.\n\nThe default \'app order\' is an algorithm implemented in this app that provides a consistently-sorted substitution plan ordered just like the substitution plan on the website used to; sorted by classes, then by time.\n\n\'Website order\' offers the items on the substitution plan just like they\'re presented on the school website, but since these are not guaranteed to be consistent, \'app order\' is picked by default.";
|
||||
"made_by_deniz" = "Developed by Deniz :)";
|
||||
"customize_course_colors" = "Customize course colors";
|
||||
|
||||
"hidden_dialog_title" = "Testing Menu";
|
||||
"hidden_dialog_desc" = "This menu is intended for testing only.";
|
||||
"invalid_code" = "Invalid code";
|
||||
"apply" = "Apply";
|
||||
|
||||
Reference in New Issue
Block a user