// // ViewController.swift // ProjectClock // // Created by Hykilpikonna on 1/6/21. // import UIKit /** Account view controller controlling the two separate view controllers */ class AccountViewController: UIViewController { @IBOutlet var vLogin: UIView! @IBOutlet var vManage: UIView! // For instance references static var this: AccountViewController! /** Called when the user switch to this tab */ override func viewDidLoad() { // Static instance reference AccountViewController.this = self // Check if already registered/logged in if localStorage.string(forKey: "id") != nil { login() } super.viewDidLoad() } /** Login from the account page */ func login() { vLogin.isHidden = true vManage.isHidden = false ManageVC.this.display() } /** Logout */ func logout() { // Remove login info ["id", "user", "pass", "family"].forEach { localStorage.removeObject(forKey: $0) } // Switch UI vLogin.isHidden = false vManage.isHidden = true } } /** View controller for registration and login */ class LoginVC: UIViewController { @IBOutlet weak var username: UITextField! @IBOutlet weak var password: UITextField! /** Send user login/registration request - Parameter login: True: Login, False: Register */ func userRequest(login: Bool) { // Verify username and password guard let name = username.text, name ~= "[A-Za-z0-9_-]{3,16}" else { msg("Username Invalid", "Username must be 3 to 16 characters long, and must only contain a-z, 0-9, underscore, and minus signs (-).") return } guard let pass = password.text, pass.count >= 8 else { msg("Password Invalid", "Password must be more than or equal to 8 characters long") return } // Error messages let errors = ["409 - [\"A0111\"]": "Account already exists, please login instead.", "401 -": "Incorrect username/password", "404 -": "Username does not exist in the database", "406 - [\"A0101\"]": "Username invalid." ] // Send register request sendReq(login ? APIs.login : APIs.register, title: login ? "Logging in..." : "Registering...", errors: errors, params: ["username": name, "password": pass.sha256]) { // Store username and password localStorage["name"] = name localStorage["pass"] = pass.sha256 localStorage["id"] = $0 send(APIs.familyGet) { $0.localSave() self.loginSuccess(login) } err: { it in print(it); self.loginSuccess(login) } } } private func loginSuccess(_ login: Bool) { ui { // Send feedback if login { self.msg("Login success!", "Now you can use account features, yay!") } else { self.msg("Registration success!", "Now you have an account, yay!") } // Hide registration and show account detail view AccountViewController.this.login() } } /** Called when the user clicks register */ @IBAction func register(_ sender: Any) { userRequest(login: false) } /** Called when the user clicks login */ @IBAction func login(_ sender: Any) { userRequest(login: true) } } /** Account manage view controller */ class ManageVC: UIViewController { static var this: ManageVC! @IBOutlet weak var lUsername: UILabel! @IBOutlet weak var lJoinDate: UILabel! @IBOutlet weak var lCurrentFamily: UILabel! /** Called when the user switched to the account tab (whether the view container is hidden or not) */ override func viewDidLoad() { // Static reference ManageVC.this = self super.viewDidLoad() } /** Display account info */ func display() { lUsername.text = localStorage.string(forKey: "name") // TODO: Implement join date (not important) lJoinDate.text = localStorage.string(forKey: "id") // Display family name if let family = Family.fromLocal() { lCurrentFamily.text = family.name } } /** Called when the user clicks the upload backup button */ @IBAction func uploadBackup(_ sender: Any) { sendReq(APIs.uploadConfig, title: "Uploading...", params: ["config": localStorage.string(forKey: "alarms")!]) { it in self.msg("Success!", "You're backed up.") } } /** Called when the user clicks the download backup button */ @IBAction func downloadBackup(_ sender: Any) { sendReq(APIs.downloadConfig, title: "Downloading...") { // Save backup localStorage.setValue($0, forKey: "alarms") // Update UI AlarmViewController.staticTable?.reloadData() self.msg("Success!", "You're restored your backup.") } } /** Called when the user clicks the logout button */ @IBAction func logout(_ sender: Any) { AccountViewController.this.logout() } /** Called when the user clicks the delete account button */ @IBAction func deleteAccount(_ sender: Any) { sendReq(APIs.delete, title: "Deleting...") { print("Deleted! \($0)") self.msg("Deleted!", "You are erased from our database, you no longer exist.") self.logout(sender) } } } /** Family view controller that displays family info or create/join family buttons */ class FamilyVC: UIViewController { static var this: FamilyVC! // No family view - prompt to create/join a family @IBOutlet weak var noFamilyView: UIView! var createMode: Bool! @IBAction func btnCreate(_ sender: Any) { createMode = true performSegue(withIdentifier: "family-create-join", sender: nil) } @IBAction func btnJoin(_ sender: Any) { createMode = false performSegue(withIdentifier: "family-create-join", sender: nil) } @IBSegueAction func segueCreateJoin(_ coder: NSCoder) -> FamilyCreateJoinVC? { return FamilyCreateJoinVC(coder: coder, create: createMode) } // Family view - Display family information and controls @IBOutlet weak var familyView: UIView! @IBOutlet weak var table: UITableView! /** Called when information is updated */ override func viewDidLoad() { FamilyVC.this = self if let _ = Family.fromLocal() { // Family exists noFamilyView.isHidden = true familyView.isHidden = false table.dataSource = self table.delegate = self } else { // Family doesn't exist noFamilyView.isHidden = false familyView.isHidden = true } } /** Called when the user clicks the refresh button */ @IBAction func btnRefresh(_ sender: Any) { sendReq(APIs.familyGet, title: "Updating family...") { $0.localSave() self.table.reloadData() } } /** Called when the user clicks the change pin button */ @IBAction func btnChangePin(_ sender: Any) { self.enterPin("Change Pin", "Enter your OLD pin:") { oldPin in self.enterPin("Change Pin", "Enter your NEW pin:") { newPin in guard newPin.count >= 4 else { self.msg("Pin Too Weak", "Your family pin must be 4 numbers or more."); return } self.sendReq(APIs.familyChangePin, title: "Updating Pin...", params: ["oldPin": oldPin, "newPin": newPin]) { it in self.msg("Update Success!", "Your family pin is updated.") } } } } /** Called when the user clicks the leave or delete family button */ @IBAction func btnLeave(_ sender: UIButton) { let i = sender.tag let action = ["Leave", "Delete"][i] let title = ["Leaving...", "Deleting..."][i] let msg = ["You left the family.", "You deleted the family."][i] enterPin() { self.sendReq(APIs.familyAction, title: title, params: ["pin": $0, "action": action]) { it in // Leave or delete, clear local storage's family section if i == 0 || i == 1 { localStorage.removeObject(forKey: "family") } self.msg("\(action) Success!", msg) { self.viewDidLoad() AccountViewController.this.login() } } } } } /** Table data source */ extension FamilyVC: UITableViewDelegate, UITableViewDataSource { /** Define row count */ func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int { guard let family = Family.fromLocal() else { return 0 } return family.membersList.count } /** Set cell at i */ func tableView(_ view: UITableView, cellForRowAt i: IndexPath) -> UITableViewCell { let cell = view.dequeueReusableCell(withIdentifier: "family-member-cell", for: i) cell.textLabel?.text = Family.fromLocal()!.membersList[i.row] return cell } /** Called when the user clicks on the cell at i */ func tableView(_ view: UITableView, didSelectRowAt i: IndexPath) { print(i.row) view.deselectRow(at: i, animated: true) } } /** Create or join a family */ class FamilyCreateJoinVC: UIViewController { let createMode: Bool @IBOutlet weak var lFamilyNameOrId: UILabel! @IBOutlet weak var bCreateJoin: UIButton! @IBOutlet weak var tNameOrId: UITextField! @IBOutlet weak var tPin: UITextField! /** Pass in create mode from FamilyVC */ init?(coder: NSCoder, create: Bool) { createMode = create super.init(coder: coder) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } /** On load */ override func viewDidLoad() { // Set UI according to createMode lFamilyNameOrId.text = createMode ? "Family Name" : "Family ID" bCreateJoin.setTitle(createMode ? "Create" : "Join", for: .normal) tNameOrId.keyboardType = createMode ? .default : .numberPad // Default name if createMode { tNameOrId.text = "\(localStorage.string(forKey: "name")!)'s Family" } } /** Called after successfully joining or creating a family. */ func afterFamilyChange() { self.dismiss() FamilyVC.this.viewDidLoad() AccountViewController.this.login() } /** Called when the user clicks create or join button */ @IBAction func btnCreateOrJoin(_ sender: Any) { // Check pin guard let pin = tPin.text, pin.count >= 4 else { msg("Pin Too Weak", "Your family pin must be 4 numbers or more."); return } if createMode { guard let name = tNameOrId.text, !name.isEmpty else { msg("Name Empty", "You must enter a family name"); return } // Create family sendReq(APIs.familyCreate, title: "Creating...", params: ["name": name, "pin": pin]) { // Save $0.localSave() // Send success message self.msg("Created!", "Your family ID is \($0.fid)") { self.afterFamilyChange() } } } else { guard let idString = tNameOrId.text, !idString.isEmpty, let id = Int(idString) else { msg("ID Incorrect", "Please make sure your ID is an positive integer."); return } // Join family sendReq(APIs.familyAction, title: "Joining...", params: ["fid": String(id), "pin": pin, "action": "join"]) { // Save $0.localSave() // Send success message self.msg("Joined!", "Your family ID is \($0.fid)") { self.afterFamilyChange() } } } } } /** View controller for adding an alarm to a fmaily member */ class FamilyAddAlarmVC: UIViewController { @IBOutlet weak var table: UITableView! override func viewDidLoad() { super.viewDidLoad() table.delegate = self table.dataSource = self } } /** Table data source */ extension FamilyAddAlarmVC: UITableViewDelegate, UITableViewDataSource { /** Define row count */ func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int { return Alarms.fromLocal().list.count } /** Set cell at i */ func tableView(_ view: UITableView, cellForRowAt i: IndexPath) -> UITableViewCell { let cell = view.dequeueReusableCell(withIdentifier: "family-alarm-cell", for: i) cell.textLabel?.text = Alarms.fromLocal().list[i.row].timeText return cell } /** Called when the user clicks on the cell at i */ func tableView(_ view: UITableView, didSelectRowAt i: IndexPath) { view.deselectRow(at: i, animated: true) } }