Added testing menu, made layout changes, optimised DataFetcher.swift, removed Alamofire dependency

This commit is contained in:
Deniz Duezgoeren
2019-10-13 16:53:08 +02:00
parent 882c6acecc
commit f34392041f
59 changed files with 3865 additions and 10543 deletions
+13 -5
View File
@@ -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"/>
+4
View File
@@ -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
View File
@@ -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
}
}
+7 -6
View File
@@ -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)
}
}
}
+15 -9
View File
@@ -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)
}
}
}
+5
View File
@@ -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";
+5
View File
@@ -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";