1 Commits

Author SHA1 Message Date
Aaron 1f66a59372 Made alarmTone editable, but localRead() nil error. 2021-01-27 22:24:54 -05:00
19 changed files with 1020 additions and 1639 deletions
+18 -26
View File
@@ -9,12 +9,11 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
4F509BD225AE22D100726227 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F509BD125AE22D100726227 /* Models.swift */; }; 4F509BD225AE22D100726227 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F509BD125AE22D100726227 /* Models.swift */; };
4F8A607125A9160400D88DC3 /* AddAlarmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8A607025A9160400D88DC3 /* AddAlarmViewController.swift */; }; 4F8A607125A9160400D88DC3 /* AddAlarmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8A607025A9160400D88DC3 /* AddAlarmViewController.swift */; };
4F8A607525A919E600D88DC3 /* Logic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8A607425A919E600D88DC3 /* Logic.swift */; };
4F98955225A9260400F51321 /* Net.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F98955125A9260400F51321 /* Net.swift */; }; 4F98955225A9260400F51321 /* Net.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F98955125A9260400F51321 /* Net.swift */; };
4FA419AF25AF93EC004CE0FC /* AlarmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA419AE25AF93EC004CE0FC /* AlarmViewController.swift */; }; 4FA419AF25AF93EC004CE0FC /* AlarmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA419AE25AF93EC004CE0FC /* AlarmViewController.swift */; };
4FD642D325B48C380069171E /* AlarmActivator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD642D225B48C380069171E /* AlarmActivator.swift */; };
4FD642DB25B4B7F60069171E /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD642DA25B4B7F60069171E /* Utils.swift */; };
4FD642E025B4D5F30069171E /* AlarmActivationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD642DF25B4D5F30069171E /* AlarmActivationViewController.swift */; };
4FF0683F25A5F18700304E6B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF0683E25A5F18700304E6B /* AppDelegate.swift */; }; 4FF0683F25A5F18700304E6B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF0683E25A5F18700304E6B /* AppDelegate.swift */; };
4FF0684125A5F18700304E6B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF0684025A5F18700304E6B /* SceneDelegate.swift */; };
4FF0684325A5F18700304E6B /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF0684225A5F18700304E6B /* AccountViewController.swift */; }; 4FF0684325A5F18700304E6B /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF0684225A5F18700304E6B /* AccountViewController.swift */; };
4FF0684625A5F18700304E6B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4FF0684425A5F18700304E6B /* Main.storyboard */; }; 4FF0684625A5F18700304E6B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4FF0684425A5F18700304E6B /* Main.storyboard */; };
4FF0684825A5F18800304E6B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4FF0684725A5F18800304E6B /* Assets.xcassets */; }; 4FF0684825A5F18800304E6B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4FF0684725A5F18800304E6B /* Assets.xcassets */; };
@@ -22,20 +21,18 @@
7C5DAE9C25AF812200E44C52 /* clock.png in Resources */ = {isa = PBXBuildFile; fileRef = 7C5DAE9B25AF812200E44C52 /* clock.png */; }; 7C5DAE9C25AF812200E44C52 /* clock.png in Resources */ = {isa = PBXBuildFile; fileRef = 7C5DAE9B25AF812200E44C52 /* clock.png */; };
7C83963625AF375B0027A94C /* NotificationLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C83963525AF375B0027A94C /* NotificationLogic.swift */; }; 7C83963625AF375B0027A94C /* NotificationLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C83963525AF375B0027A94C /* NotificationLogic.swift */; };
7C83963925AF68980027A94C /* TestingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C83963825AF68980027A94C /* TestingViewController.swift */; }; 7C83963925AF68980027A94C /* TestingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C83963825AF68980027A94C /* TestingViewController.swift */; };
C7E638E825B88F8B00799512 /* MathExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7E638E725B88F8B00799512 /* MathExpressions.swift */; }; 7C83963C25AF6B6B0027A94C /* Alarm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C83963B25AF6B6B0027A94C /* Alarm.swift */; };
F0DF7C0725BCD9FC0064A26B /* StopwatchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
4F509BD125AE22D100726227 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = "<group>"; }; 4F509BD125AE22D100726227 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = "<group>"; };
4F8A607025A9160400D88DC3 /* AddAlarmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAlarmViewController.swift; sourceTree = "<group>"; }; 4F8A607025A9160400D88DC3 /* AddAlarmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAlarmViewController.swift; sourceTree = "<group>"; };
4F8A607425A919E600D88DC3 /* Logic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logic.swift; sourceTree = "<group>"; };
4F98955125A9260400F51321 /* Net.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Net.swift; sourceTree = "<group>"; }; 4F98955125A9260400F51321 /* Net.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Net.swift; sourceTree = "<group>"; };
4FA419AE25AF93EC004CE0FC /* AlarmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmViewController.swift; sourceTree = "<group>"; }; 4FA419AE25AF93EC004CE0FC /* AlarmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmViewController.swift; sourceTree = "<group>"; };
4FD642D225B48C380069171E /* AlarmActivator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmActivator.swift; sourceTree = "<group>"; };
4FD642DA25B4B7F60069171E /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
4FD642DF25B4D5F30069171E /* AlarmActivationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmActivationViewController.swift; sourceTree = "<group>"; };
4FF0683B25A5F18700304E6B /* ProjectClock.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectClock.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4FF0683B25A5F18700304E6B /* ProjectClock.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectClock.app; sourceTree = BUILT_PRODUCTS_DIR; };
4FF0683E25A5F18700304E6B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 4FF0683E25A5F18700304E6B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
4FF0684025A5F18700304E6B /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
4FF0684225A5F18700304E6B /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = "<group>"; }; 4FF0684225A5F18700304E6B /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = "<group>"; };
4FF0684525A5F18700304E6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; 4FF0684525A5F18700304E6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
4FF0684725A5F18800304E6B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 4FF0684725A5F18800304E6B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -46,8 +43,7 @@
7C83962F25AF34F10027A94C /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; }; 7C83962F25AF34F10027A94C /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; };
7C83963525AF375B0027A94C /* NotificationLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationLogic.swift; sourceTree = "<group>"; }; 7C83963525AF375B0027A94C /* NotificationLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationLogic.swift; sourceTree = "<group>"; };
7C83963825AF68980027A94C /* TestingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestingViewController.swift; sourceTree = "<group>"; }; 7C83963825AF68980027A94C /* TestingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestingViewController.swift; sourceTree = "<group>"; };
C7E638E725B88F8B00799512 /* MathExpressions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MathExpressions.swift; sourceTree = "<group>"; }; 7C83963B25AF6B6B0027A94C /* Alarm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alarm.swift; sourceTree = "<group>"; };
F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopwatchViewController.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -81,25 +77,23 @@
4FF0683D25A5F18700304E6B /* ProjectClock */ = { 4FF0683D25A5F18700304E6B /* ProjectClock */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7C5DAE9B25AF812200E44C52 /* clock.png */,
4FF0684C25A5F18800304E6B /* Info.plist */,
7C83962D25AF34F00027A94C /* ProjectClock.entitlements */,
4FF0684925A5F18800304E6B /* LaunchScreen.storyboard */,
4FF0684725A5F18800304E6B /* Assets.xcassets */,
4FF0683E25A5F18700304E6B /* AppDelegate.swift */, 4FF0683E25A5F18700304E6B /* AppDelegate.swift */,
4FF0684025A5F18700304E6B /* SceneDelegate.swift */,
7C83962D25AF34F00027A94C /* ProjectClock.entitlements */,
4FF0684225A5F18700304E6B /* AccountViewController.swift */, 4FF0684225A5F18700304E6B /* AccountViewController.swift */,
4FA419AE25AF93EC004CE0FC /* AlarmViewController.swift */, 4FA419AE25AF93EC004CE0FC /* AlarmViewController.swift */,
4F8A607025A9160400D88DC3 /* AddAlarmViewController.swift */, 4F8A607025A9160400D88DC3 /* AddAlarmViewController.swift */,
7C83963825AF68980027A94C /* TestingViewController.swift */,
4FD642DF25B4D5F30069171E /* AlarmActivationViewController.swift */,
4FD642D225B48C380069171E /* AlarmActivator.swift */,
4FF0684425A5F18700304E6B /* Main.storyboard */, 4FF0684425A5F18700304E6B /* Main.storyboard */,
F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */, 4FF0684725A5F18800304E6B /* Assets.xcassets */,
4FF0684925A5F18800304E6B /* LaunchScreen.storyboard */,
4FF0684C25A5F18800304E6B /* Info.plist */,
4F8A607425A919E600D88DC3 /* Logic.swift */,
7C83963525AF375B0027A94C /* NotificationLogic.swift */, 7C83963525AF375B0027A94C /* NotificationLogic.swift */,
4F98955125A9260400F51321 /* Net.swift */, 4F98955125A9260400F51321 /* Net.swift */,
7C5DAE9B25AF812200E44C52 /* clock.png */,
4F509BD125AE22D100726227 /* Models.swift */, 4F509BD125AE22D100726227 /* Models.swift */,
C7E638E725B88F8B00799512 /* MathExpressions.swift */, 7C83963825AF68980027A94C /* TestingViewController.swift */,
4FD642DA25B4B7F60069171E /* Utils.swift */, 7C83963B25AF6B6B0027A94C /* Alarm.swift */,
); );
path = ProjectClock; path = ProjectClock;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -188,17 +182,15 @@
files = ( files = (
4F8A607125A9160400D88DC3 /* AddAlarmViewController.swift in Sources */, 4F8A607125A9160400D88DC3 /* AddAlarmViewController.swift in Sources */,
4F98955225A9260400F51321 /* Net.swift in Sources */, 4F98955225A9260400F51321 /* Net.swift in Sources */,
4F8A607525A919E600D88DC3 /* Logic.swift in Sources */,
7C83963925AF68980027A94C /* TestingViewController.swift in Sources */, 7C83963925AF68980027A94C /* TestingViewController.swift in Sources */,
4FF0684325A5F18700304E6B /* AccountViewController.swift in Sources */, 4FF0684325A5F18700304E6B /* AccountViewController.swift in Sources */,
F0DF7C0725BCD9FC0064A26B /* StopwatchViewController.swift in Sources */,
4FF0683F25A5F18700304E6B /* AppDelegate.swift in Sources */, 4FF0683F25A5F18700304E6B /* AppDelegate.swift in Sources */,
4FD642D325B48C380069171E /* AlarmActivator.swift in Sources */, 4FF0684125A5F18700304E6B /* SceneDelegate.swift in Sources */,
4FD642DB25B4B7F60069171E /* Utils.swift in Sources */,
4FA419AF25AF93EC004CE0FC /* AlarmViewController.swift in Sources */, 4FA419AF25AF93EC004CE0FC /* AlarmViewController.swift in Sources */,
7C83963C25AF6B6B0027A94C /* Alarm.swift in Sources */,
4F509BD225AE22D100726227 /* Models.swift in Sources */, 4F509BD225AE22D100726227 /* Models.swift in Sources */,
C7E638E825B88F8B00799512 /* MathExpressions.swift in Sources */,
7C83963625AF375B0027A94C /* NotificationLogic.swift in Sources */, 7C83963625AF375B0027A94C /* NotificationLogic.swift in Sources */,
4FD642E025B4D5F30069171E /* AlarmActivationViewController.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
-163
View File
@@ -7,173 +7,10 @@
import UIKit import UIKit
/**
Account view controller controlling the two separate view controllers
*/
class AccountViewController: UIViewController 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() override func viewDidLoad()
{ {
// Static instance reference
AccountViewController.this = self
// Check if already registered/logged in
if localStorage.string(forKey: "id") != nil { login() }
super.viewDidLoad() 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"].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 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!
/**
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: Correct join date
lJoinDate.text = localStorage.string(forKey: "id")
}
/**
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)
}
}
} }
+88 -36
View File
@@ -9,6 +9,74 @@ import UIKit
class AddAlarmViewController: UIViewController class AddAlarmViewController: UIViewController
{ {
<<<<<<< Updated upstream
=======
// Editing variables
var alarmCell: AlarmTableCell? = nil
var editMode: Bool { alarmCell != nil }
var originalTime: String = ""
override func viewDidLoad()
{
// End edit on return
alarmNameTextField.delegate = self
// Load alarm to edit if in edit mode
if let alarmCell = alarmCell
{
// Toggle editing mode
viewTitle.text = "Edit Alarm"
// Convert string to Date
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "h:mma"
let date = dateFormatter.date(from: "\(alarmCell.time.text!)\(alarmCell.ampm.text!)")
// Set all the original values to be edited
timePicker.date = date!
originalTime = String(dateFormatter.string(from: date!).dropLast(2))
// Toggle proper repeats
if let repeats = alarmCell.repeatText.text {
if repeats == "Repeats: Weekdays" {
repeatWeekdaysSwitch.isOn = true
repeatWeekendsSwitch.isOn = false
} else if repeats == "Repeats: Weekends" {
repeatWeekendsSwitch.isOn = true
repeatWeekdaysSwitch.isOn = false
} else if repeats == "Repeats: Daily" {
repeatWeekdaysSwitch.isOn = true
repeatWeekendsSwitch.isOn = true
} else {
repeatWeekendsSwitch.isOn = false
repeatWeekdaysSwitch.isOn = false
}
}
alarmNameTextField.text = String(alarmCell.descriptionText.text!.dropFirst(2))
updateETA()
// Sets the WVM
if let wvm = alarmCell.wvmText.text {
for index in 0...wvms.count-1 {
if wvm == wvms[index].name {
wvmPicker.selectRow(index, inComponent: 0, animated: true)
}
}
}
//Sets alarm tone
if let toneName = alarmCell.toneLabel.text {
for index in 0...ringtones.count-1 {
if toneName == ringtones[index].name {
ringtonePicker.selectRow(index, inComponent: 0, animated: true)
}
}
}
}
}
>>>>>>> Stashed changes
// UI: Make scroll view scrollable // UI: Make scroll view scrollable
@IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var scrollViewInner: UIView! @IBOutlet weak var scrollViewInner: UIView!
@@ -17,55 +85,39 @@ class AddAlarmViewController: UIViewController
scrollView.addSubview(scrollViewInner) scrollView.addSubview(scrollViewInner)
scrollView.contentSize = scrollViewInner.frame.size scrollView.contentSize = scrollViewInner.frame.size
} }
// Pickers // Pickers
@IBOutlet weak var timePicker: UIDatePicker! @IBOutlet weak var timePicker: UIDatePicker!
@IBOutlet weak var wvmPicker: UIPickerView! @IBOutlet weak var wvmPicker: UIPickerView!
// UI Elements override func viewDidLoad()
@IBOutlet weak var repeatWeekdaysSwitch: UISwitch!
@IBOutlet weak var repeatWeekendsSwitch: UISwitch!
@IBOutlet weak var alarmNameTextField: UITextField!
@IBAction func defaultRingtonesButton(_ sender: Any)
{ {
<<<<<<< Updated upstream
} super.viewDidLoad()
=======
@IBAction func soundLibraryButton(_ sender: Any)
{
}
/**
Called when the user clicks the remove button and brings them back to the home page
*/
@IBAction func cancelAlarmButton(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
//might need to reset all UI elements
}
/**
Called when the user clicks Add Alarm
*/
@IBAction func addAlarmButton(_ sender: Any) {
let (h, m, _) = timePicker.date.getHMS() let (h, m, _) = timePicker.date.getHMS()
// Create the alarm // Create the alarm
let alarm = Alarm(hour: h, minute: m, let alarm = Alarm(hour: h, minute: m,
text: alarmNameTextField.text ?? "Alarm", text: alarmNameTextField.text ?? "Alarm",
wakeMethod: wvms[wvmPicker.selectedRow(inComponent: 0)], wakeMethod: wvms[wvmPicker.selectedRow(inComponent: 0)],
lastActivate: Date()) lastActivate: Date(), alarmTone: ringtones[ringtonePicker.selectedRow(inComponent: 0)].tone, toneName: ringtones[ringtonePicker.selectedRow(inComponent: 0)].name)
// TODO: Set alarm.repeats to correspond with what the user selects // Set alarm.repeats to correspond with what the user selects
(0...6).forEach { alarm.repeats[$0] = false }
if repeatWeekdaysSwitch.isOn { (1...5).forEach { alarm.repeats[$0] = true } }
if repeatWeekendsSwitch.isOn { [0, 6].forEach { alarm.repeats[$0] = true } }
return alarm
// Add the alarm to the list and save the list }
Alarms.fromLocal().apply { $0.list.append(alarm) }.localSave();
/**
// Dismiss this view Dynamically the ETA label for the alarm
self.dismiss(animated: true, completion: nil) */
func updateETA() {
let timeTill = createAlarm().nextActivate!.timeIntervalSince(Date()).str()
timeTillAlarmLabel.text = "Going off in \(timeTill)"
>>>>>>> Stashed changes
} }
} }
+22
View File
@@ -0,0 +1,22 @@
//
// Alarm.swift
// ProjectClock
//
// Created by Aaron Saporito on 1/13/21.
//
import Foundation
class Alarm {
var alarmTime: Date
var text: String
var wakeMethod: WVM
init(alarmTime: Date, text: String, wakeMethod: WVM) {
self.alarmTime = alarmTime
self.text = text
self.wakeMethod = wakeMethod
}
}
@@ -1,122 +0,0 @@
//
// AlarmActivationViewController.swift
// ProjectClock
//
// Created by Hykilpikonna on 1/17/21.
//
import UIKit
import AVFoundation
class AlarmActivationViewController: UIViewController
{
var timer: Timer?
var currentAlarm: Alarm?
//Puzzle outlets
@IBOutlet weak var puzzleView: UIView!
@IBOutlet weak var puzzleQuestionLabel: UILabel!
@IBOutlet weak var puzzleAnswerInput: UITextField!
var puzzleAnswers: [Int] = []
//RPS Outlets
@IBOutlet weak var rpsView: UIView!
@IBOutlet weak var rpsResult: UILabel!
init?(coder: NSCoder, currentAlarm: Alarm)
{
self.currentAlarm = currentAlarm
//print(currentAlarm.wakeMethod)
super.init(coder: coder)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad()
{
super.viewDidLoad()
//Hide all inactive wakemethods
puzzleView.isHidden = true
rpsView.isHidden = true
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(AlarmActivationViewController.playSound), userInfo: nil, repeats: true)
setAlarmType()
//print(MathExpression.random())
}
@objc func playSound()
{
AudioServicesPlayAlertSound(SystemSoundID(1005))
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
}
func setAlarmType()
{
if let alarm = currentAlarm
{
switch alarm.wakeMethod.name {
case "Walk":
walkAction()
case "Jump":
jumpAction()
case "Factor":
self.puzzleAnswers = factorAction(puzzleQuestionLabel: puzzleQuestionLabel)
puzzleView.isHidden = false
case "Smash":
print("")
case "RPS":
rpsView.isHidden = false
//Get Choice here
//rpsAction(choice: choice)
default:
print("Invalid alarm type")
}
}
}
//Verfies and ends factoring WVM
@IBAction func checkBinomialSolution(_ sender: Any) {
if let input = puzzleAnswerInput.text {
if let numericalInput = Int(input) {
if puzzleAnswers.contains(numericalInput) {
endAlarm()
}
}
}
}
//Gets RPS choice
@IBAction func rockChoice(_ sender: Any) {
if rpsAction(choice: .rock)! {
endAlarm()
} else {
rpsResult.text = "Paper: You lost, try again"
}
}
@IBAction func paperChoice(_ sender: Any) {
if rpsAction(choice: .paper)! {
endAlarm()
} else {
rpsResult.text = "Scissors: You lost, try again"
}
}
@IBAction func scissorChoice(_ sender: Any) {
if rpsAction(choice: .scissors)! {
endAlarm()
} else {
rpsResult.text = "Rock: You lost, try again"
}
}
//Standard way to turn off and close the alarm
func endAlarm() {
timer?.invalidate()
print("Alarm solved")
dismiss(animated: true, completion: nil)
}
}
-76
View File
@@ -1,76 +0,0 @@
//
// AlarmActivator.swift
// ProjectClock
//
// Created by Hykilpikonna on 1/17/21.
//
import Foundation
import UIKit
/**
Class to activate alarms when the user is inside the app
Note: This will not run when app is switched to the background or when the display is turned off, but it will run right after the user switched back to the app.
*/
class AlarmActivator: UITabBarController
{
/// Interval in seconds
static var interval = 2.0
/// Timer for scheduled calls
var timer: Timer?
var alarm: Alarm?
override func viewDidLoad()
{
start()
}
/**
Start detecting alarms
*/
func start()
{
if timer != nil { return }
timer = Timer.scheduledTimer(timeInterval: AlarmActivator.interval, target: self, selector: #selector(AlarmActivator.check), userInfo: nil, repeats: true)
}
/**
Stop detecting alarms
*/
func stop()
{
timer?.invalidate()
timer = nil
}
/**
Check alarm
*/
@objc func check()
{
//NSLog("Check")
// Get the alarm to activate
let alarms = Alarms.fromLocal()
guard let alarm = alarms.listActivating.first else { return }
// Update alarm info
alarm.apply {
$0.lastActivate = Date()
if $0.oneTime { $0.enabled = false }
}
alarms.localSave()
self.alarm = alarm
// Segue
//NSLog(JSON.stringify(alarm)!)
performSegue(withIdentifier: "activate-alarm", sender: alarm)
}
@IBSegueAction func sendAlarm(_ coder: NSCoder) -> AlarmActivationViewController? {
return AlarmActivationViewController(coder: coder, currentAlarm: alarm!)
}
}
+48 -10
View File
@@ -3,14 +3,15 @@ import UIKit
class AlarmViewController: UIViewController class AlarmViewController: UIViewController
{ {
@IBOutlet weak var table: UITableView! @IBOutlet weak var table: UITableView!
static var staticTable: UITableView?
// TODO: Remove example and use localStorage
var data: [Alarm] = [Alarm(alarmTime: Date(), text: "Wake up lol", wakeMethod: wvms[0])]
override func viewDidLoad() override func viewDidLoad()
{ {
super.viewDidLoad() super.viewDidLoad()
// Assign table delegate and data source // Assign table delegate and data source
AlarmViewController.staticTable = table
table.delegate = self table.delegate = self
table.dataSource = self table.dataSource = self
} }
@@ -25,7 +26,7 @@ extension AlarmViewController: UITableViewDelegate, UITableViewDataSource
func numberOfSections(in: UITableView) -> Int { return 1 } func numberOfSections(in: UITableView) -> Int { return 1 }
/// How many rows are there /// How many rows are there
func tableView(_ v: UITableView, numberOfRowsInSection s: Int) -> Int { return Alarms.fromLocal().list.count } func tableView(_ v: UITableView, numberOfRowsInSection s: Int) -> Int { return data.count }
/// Configure each cell /// Configure each cell
func tableView(_ v: UITableView, cellForRowAt i: IndexPath) -> UITableViewCell func tableView(_ v: UITableView, cellForRowAt i: IndexPath) -> UITableViewCell
@@ -33,7 +34,7 @@ extension AlarmViewController: UITableViewDelegate, UITableViewDataSource
// Get the cell and item at index i // Get the cell and item at index i
let rawCell = v.dequeueReusableCell(withIdentifier: "alarm", for: i) let rawCell = v.dequeueReusableCell(withIdentifier: "alarm", for: i)
guard let cell = rawCell as? AlarmTableCell else { return rawCell } guard let cell = rawCell as? AlarmTableCell else { return rawCell }
let item = Alarms.fromLocal().list[i.row] let item = data[i.row]
// Set the content of the cell to the content of the item. // Set the content of the cell to the content of the item.
cell.setData(item) cell.setData(item)
@@ -59,35 +60,72 @@ class AlarmTableCell: UITableViewCell
@IBOutlet weak var enable: UISwitch! @IBOutlet weak var enable: UISwitch!
@IBOutlet weak var repeatText: UILabel! @IBOutlet weak var repeatText: UILabel!
@IBOutlet weak var goingOffText: UILabel! @IBOutlet weak var goingOffText: UILabel!
<<<<<<< Updated upstream
=======
@IBOutlet weak var wvmText: UILabel!
@IBOutlet weak var toneLabel: UILabel!
>>>>>>> Stashed changes
/// Update information on the cell to information in the alarm object /// Update information on the cell to information in the alarm object
//WARNING:Terrible code lies ahead! You WILL be dissapointed! But it works, and that is all that matters.
func setData(_ alarm: Alarm) func setData(_ alarm: Alarm)
{ {
descriptionText.text = "- " + alarm.text descriptionText.text = "- " + alarm.text
<<<<<<< Updated upstream
=======
enable.isOn = alarm.enabled
wvmText.text = alarm.wakeMethod.name
toneLabel.text = alarm.toneName
// Display Hour, Minute, and AM or PM // Display Hour, Minute, and AM or PM
ampm.text = alarm.hour < 12 || alarm.hour == 24 ? "AM" : "PM" ampm.text = alarm.hour < 12 || alarm.hour == 24 ? "AM" : "PM"
let hour = alarm.hour <= 12 ? alarm.hour : alarm.hour - 12 var hour = alarm.hour <= 12 ? alarm.hour : alarm.hour - 12
time.text = alarm.minute < 10 ? "\(hour):0\(alarm.minute)" : "\(hour):\(alarm.minute)" hour = alarm.hour == 0 ? 12 : hour
time.text = String(format: "%i:%02i", hour, alarm.minute)
// displays the specific days alarm is activated // displays the specific days alarm is activated
let daysDict = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"] let daysDict = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"]
var daysActive : [String] = [] var daysActive : [String] = []
if alarm.oneTime {repeatText.text = "No Repeat"} if alarm.oneTime {repeatText.text = "One-time Alarm"}
else { else {
for (index, element) in alarm.repeats.enumerated() { for (index, element) in alarm.repeats.enumerated() {
if element { if element {
daysActive.append(daysDict[index]) daysActive.append(daysDict[index])
} }
} }
repeatText.text = daysActive.joined(separator: ", ") if daysDict == daysActive {
repeatText.text = "Repeats: Daily"
} else if daysActive == ["Sun", "Sat"] {
repeatText.text = "Repeats: Weekends"
} else if daysActive == ["Mon", "Tues", "Wed", "Thurs", "Fri"] {
repeatText.text = "Repeats: Weekdays"
} else {
repeatText.text = daysActive.joined(separator: ", ")
}
} }
updateActivationTime()
}
func updateActivationTime()
{
// Show next activation date // Show next activation date
if alarm.enabled, let n = alarm.nextActivate { if alarm.enabled, let n = alarm.nextActivate {
goingOffText.text = "(Going off in \(n.timeIntervalSince(Date()).str()))" goingOffText.text = "(Going off in \(n.timeIntervalSince(Date()).str()))"
} }
else {
goingOffText.text = ""
}
}
/**
Called when the user switches the switch
*/
@IBAction func switchChange(_ sender: Any)
{
Alarms.fromLocal().apply {
$0.list.first { $0.hour == self.alarm.hour && $0.minute == self.alarm.minute }?.enabled = enable.isOn
}.localSave()
>>>>>>> Stashed changes
} }
} }
+13 -60
View File
@@ -8,76 +8,29 @@
import UIKit import UIKit
@main @main
class AppDelegate: UIResponder, UIApplicationDelegate class AppDelegate: UIResponder, UIApplicationDelegate {
{
/// Override point for customization after application launch.
func application(_ app: UIApplication, didFinishLaunchingWithOptions op: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Init default settings // Override point for customization after application launch.
localStorage.register(defaults: [
"alarms": JSON.stringify([Alarm(hour: 7, minute: 20, text: "Wake up lol", wakeMethod: wvms[0])])!
])
return true return true
} }
func application(_ app: UIApplication, configurationForConnecting session: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration // MARK: UISceneSession Lifecycle
{
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created. // Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with. // Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: session.role) return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
} }
func application(_ app: UIApplication, didDiscardSceneSessions sessions: Set<UISceneSession>) func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
{
// Called when the user discards a scene session. // Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return. // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
} }
} }
class SceneDelegate: UIResponder, UIWindowSceneDelegate
{
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
{
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene)
{
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene)
{
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene)
{
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene)
{
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene)
{
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
File diff suppressed because it is too large Load Diff
-13
View File
@@ -2,19 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
+20
View File
@@ -0,0 +1,20 @@
//
// ProjectClock
//
// Created by Hykilpikonna on 1/8/21.
//
import Foundation
struct WVM
{
let name: String
let desc: String
}
let wvms = [
WVM(name: "Walk", desc: "Walk a few steps"),
WVM(name: "Jump", desc: "Make a few jumps"),
WVM(name: "Puzzle", desc: "Complete a simple puzzle"),
WVM(name: "Smash", desc: "It'll never truns off"),
]
-198
View File
@@ -1,198 +0,0 @@
//
// MathExpressions.swift
// ProjectClock
//
// Puzzles to complete for task (math or RPS)
//
import Foundation
/**
Math element for problem generation (Credit: https://stackoverflow.com/a/43132311/7346633)
*/
enum MathElement : CustomStringConvertible {
case Integer(value: Int)
case Percentage(value: Int)
case Expression(expression: MathExpression)
var description: String {
switch self {
case .Integer(let value): return "\(value)"
case .Percentage(let percentage): return "\(percentage)%"
case .Expression(let expr): return expr.description
}
}
var nsExpressionFormatString : String {
switch self {
case .Integer(let value): return "\(value).0"
case .Percentage(let percentage): return "\(Double(percentage) / 100)"
case .Expression(let expr): return "(\(expr.description))"
}
}
}
/**
Math operator for problem generation (Credit: https://stackoverflow.com/a/43132311/7346633)
*/
enum MathOperator : String {
case plus = "+"
case minus = "-"
case multiply = "*"
case divide = "/"
case power = "**"
static func random() -> MathOperator {
let allMathOperators: [MathOperator] = [.plus, .minus, .multiply, .divide, .power]
let index = Int(arc4random_uniform(UInt32(allMathOperators.count)))
return allMathOperators[index]
}
}
/**
Math expressions for problem generation (Credit: https://stackoverflow.com/a/43132311/7346633)
*/
class MathExpression : CustomStringConvertible {
var lhs: MathElement
var rhs: MathElement
var op: MathOperator
init(lhs: MathElement, rhs: MathElement, op: MathOperator) {
self.lhs = lhs
self.rhs = rhs
self.op = op
}
var description: String {
var leftString = ""
var rightString = ""
if case .Expression(_) = lhs {
leftString = "(\(lhs))"
} else {
leftString = lhs.description
}
if case .Expression(_) = rhs {
rightString = "(\(rhs))"
} else {
rightString = rhs.description
}
return "\(leftString) \(self.op.rawValue) \(rightString)"
}
var result : Int? {
let format = "\(lhs.nsExpressionFormatString) \(op.rawValue) \(rhs.nsExpressionFormatString)"
let expr = NSExpression(format: format)
return expr.expressionValue(with: nil, context: nil) as? Int
}
static func random() -> MathExpression {
let lhs = MathElement.Integer(value: Int(arc4random_uniform(10)))
let rhs = MathElement.Integer(value: Int(arc4random_uniform(10)))
return MathExpression(lhs: lhs, rhs: rhs, op: .random())
}
}
/**
Generate simple problem - 2 expressions
*/
class AlgProb2 : MathExpression {
let a = MathExpression.random()
let b = MathExpression.random()
func getProblem() -> String {
return "\(a) + \(b)"
}
func getAnswer() -> String {
return "\(a.result! + b.result!)"
}
}
/**
Generate simple problem - 3 expressions
*/
class AlgProb3 : MathExpression {
let a = MathExpression.random()
let b = MathExpression.random()
let c = MathExpression.random()
func getProblem() -> String {
return "\(a) + \(b) + \(c)"
}
func getAnswer() -> String {
return "\(a.result! + b.result! + c.result!)"
}
}
/**
Generate quadratic factorization problem
*/
class QuadraticProb {
// Generates the roots
let root1 = Int.random(in: 1...10)
let root2 = Int.random(in: 1...10)
/**
Generate problem description
*/
func getProblem() -> String {
//a is 1
let b = root1 + root2 //bx
let c = root1 * root2 //x
return "x^2 + \(b)x + \(c)"
}
/**
Finds the roots of the quadratic **NOTE**: the return type is [Int], not a String
*/
func getAnswer() -> [Int] {
return [root1, root2]
}
}
class RPS {
//@IBOutlet weak var resultsLabel: UILabel!
enum Choice: String {
case rock = "ROCK"
case paper = "PAPER"
case scissors = "SCISSORS"
}
static func randomComputerChoice() -> Choice {
let choices: [Choice] = [.rock, .paper, .scissors]
return choices[Int.random(in: 0...2)]
}
func playRPS(you: Choice, computer: Choice) -> Bool? {
if you == .rock && computer == .scissors { return true }
else if you == .paper && computer == .rock { return true}
else if you == .scissors && computer == .paper { return true }
else {
return false
}
}
/*
@IBAction func rock(_ sender: UIButton) {
let computerChoice = Choice.randomComputerChoice()
resultsLabel.text = playRPS(you: .rock, computer: computerChoice)
}
@IBAction func paper(_ sender: UIButton) {
let computerChoice = Choice.randomComputerChoice()
resultsLabel.text = playRPS(you: .paper, computer: computerChoice)
}
@IBAction func scissors(_ sender: UIButton) {
let computerChoice = Choice.randomComputerChoice()
resultsLabel.text = playRPS(you: .scissors, computer: computerChoice)
}
*/
}
+71 -17
View File
@@ -7,20 +7,31 @@
import Foundation import Foundation
struct User: Codable struct User: Decodable
{ {
var id: Int var id: Int
var name: String var name: String
<<<<<<< Updated upstream
var email: String var email: String
var pass: String var pass: String
} =======
var members: String
struct Family: Codable
{
var fid: Int
var fname: String
var members: [String]
// And a hidden field: admin pin // And a hidden field: admin pin
var membersList: [String] { members.csv }
/// Save family to local storage
func localSave()
{
localStorage.setValue(JSON.stringify(self)!, forKey: "family")
}
/// Read family object from local storage
static func fromLocal() -> Family?
{
guard let f = localStorage.string(forKey: "family") else { return nil }
return JSON.parse(Family.self, f)
}
} }
struct WVM: Codable struct WVM: Codable
@@ -29,28 +40,58 @@ struct WVM: Codable
let desc: String let desc: String
} }
let wvms = [ let wvms = [
WVM(name: "Shake", desc: "Shake your phone... aggresively!"),
WVM(name: "Math 1", desc: "Easy math expression"),
WVM(name: "Math 2", desc: "Medium math expression"),
WVM(name: "Math 3", desc: "Hard math expression"),
WVM(name: "Factor", desc: "Factor a binomial"), WVM(name: "Factor", desc: "Factor a binomial"),
WVM(name: "RPS", desc: "Win a game of rock paper scissors"), WVM(name: "RPS", desc: "Win a game of rock paper scissors"),
WVM(name: "Smash", desc: "It'll never turn off"), //WVM(name: "Smash", desc: "It'll never turn off"),
WVM(name: "Walk", desc: "Walk a few steps"), //WVM(name: "Walk", desc: "Walk a few steps"),
WVM(name: "Jump", desc: "Make a few jumps") //WVM(name: "Jump", desc: "Make a few jumps")
] ]
class Alarm: Codable
struct Tone: Codable{
let name: String
let tone: SystemSoundID
}
let ringtones = [
Tone(name: "News Flash", tone: SystemSoundID(1028)),
Tone(name: "Sherwood Forest", tone: SystemSoundID(1030)),
Tone(name: "Ladder", tone: SystemSoundID(1326)),
Tone(name: "Minuet", tone: SystemSoundID(1327)),
Tone(name: "Tock", tone: SystemSoundID(1306)),
Tone(name: "Bloom", tone: SystemSoundID(1321)),
Tone(name: "Calypso", tone: SystemSoundID(1322)),
Tone(name: "Train", tone: SystemSoundID(1323)),
Tone(name: "Fanfare", tone: SystemSoundID(1325))
]
class Alarm: Codable, Equatable
{ {
static func == (lhs: Alarm, rhs: Alarm) -> Bool {
return lhs.hour == rhs.hour && lhs.minute == rhs.minute && lhs.text == rhs.text &&
lhs.alarmTone == rhs.alarmTone && lhs.repeats == rhs.repeats
}
var enabled: Bool var enabled: Bool
var hour: Int // Hour (24) var hour: Int // Hour (24)
var minute: Int var minute: Int
var text: String var text: String
var wakeMethod: WVM var wakeMethod: WVM
var alarmTone: SystemSoundID
var notificationID: String
var toneName: String
/// What days does it repeat (Sun, Mon, Tue, Wed, Thu, Fri, Sat) /// What days does it repeat (Sun, Mon, Tue, Wed, Thu, Fri, Sat)
var repeats: [Bool] var repeats: [Bool]
/// Does it automatically disable after activating once
var oneTime: Bool
/// When is the last time that the alarm went off /// When is the last time that the alarm went off
var lastActivate: Date var lastActivate: Date
@@ -58,7 +99,10 @@ class Alarm: Codable
init(enabled: Bool = true, init(enabled: Bool = true,
hour: Int, minute: Int, text: String, wakeMethod: WVM, hour: Int, minute: Int, text: String, wakeMethod: WVM,
repeats: [Bool] = [false, true, true, true, true, true, false], repeats: [Bool] = [false, true, true, true, true, true, false],
oneTime: Bool = false, lastActivate: Date = Date() lastActivate: Date = Date(),
alarmTone: SystemSoundID = ringtones[0].tone,
toneName: String = ""
) )
{ {
self.enabled = enabled self.enabled = enabled
@@ -67,10 +111,18 @@ class Alarm: Codable
self.text = text self.text = text
self.wakeMethod = wakeMethod self.wakeMethod = wakeMethod
self.repeats = repeats self.repeats = repeats
self.oneTime = oneTime
self.lastActivate = lastActivate self.lastActivate = lastActivate
self.alarmTone = alarmTone
self.notificationID = "notification.id.\(Int.random(in: 1...Int.max))"
self.toneName = toneName
} }
/// Does it automatically disable after activating once
var oneTime: Bool { repeats.allSatisfy { !$0 } }
/// Get time in h:mm format
var timeText: String { String(format: "%i:%02i", hour, minute) }
/// When should the alarm activate next since lastActivate? /// When should the alarm activate next since lastActivate?
var nextActivate: Date? var nextActivate: Date?
{ {
@@ -103,6 +155,7 @@ class Alarms: Codable
/// Save alarms to local storage /// Save alarms to local storage
func localSave() func localSave()
{ {
list.sort { ($0.hour * 60 + $0.minute) < ($1.hour * 60 + $1.minute) }
localStorage.setValue(JSON.stringify(list)!, forKey: "alarms") localStorage.setValue(JSON.stringify(list)!, forKey: "alarms")
// Reload table view // Reload table view
@@ -124,4 +177,5 @@ class Alarms: Codable
let now = Date() let now = Date()
return listEnabled.filter { guard let n = $0.nextActivate else { return false }; return n < now } return listEnabled.filter { guard let n = $0.nextActivate else { return false }; return n < now }
} }
>>>>>>> Stashed changes
} }
+24 -119
View File
@@ -7,27 +7,8 @@
import Foundation import Foundation
/// Base URL of the HTTP server /// Base URL of the HTTP server
let baseUrl = "https://alarm-clock-api.hydev.org" let baseUrl = "http://localhost:8080/api" // TODO: Production settings
let JSON = JSONDecoder()
/// Json class
class JSON
{
static let decoder = JSONDecoder()
static let encoder = JSONEncoder()
static func stringify<T: Encodable>(_ o: T) -> String?
{
guard let jsonData = try? encoder.encode(o) else { return nil }
return String(data: jsonData, encoding: String.Encoding.utf8)
}
static func parse<T: Decodable>(_ type: T.Type, _ j: String) -> T?
{
return try? decoder.decode(type, from: j.data(using: .utf8)!)
}
}
/// Local storage
let localStorage = UserDefaults(suiteName: "group.org.hydev.alarm.clock")! let localStorage = UserDefaults(suiteName: "group.org.hydev.alarm.clock")!
/// API class /// API class
@@ -43,32 +24,21 @@ class APIs
Register the user in the database. Register the user in the database.
## Parameters ## Parameters
- username: The user's unique username - name: The user's name (this is not username because it doesn't have to be unique)
- password: Password hash - email: The user's email (this does have to be unique)
- pass: Password (initial hash)
## Returns ## Returns
Success or error Success or error
*/ */
static let register = API<String>(loc: "/user/register") static let register = API<String>(loc: "/user/register")
/**
Verify password and login.
## Parameters
- username: The user's unique username
- password: Password hash
## Returns
Success or error
*/
static let login = API<String>(loc: "/user/login")
/** /**
Delete a user from the database. Delete a user from the database.
## Parameters ## Parameters
- username: The user's unique username - email: The user's email
- password: Password hash - pass: Password (initial hash)
## Returns ## Returns
Success or error Success or error
@@ -78,7 +48,7 @@ class APIs
/** /**
Upload curent config to the cloud. Upload curent config to the cloud.
## Parameters (Besides from username and password) ## Parameters
- config: The config json - config: The config json
## Returns ## Returns
@@ -89,7 +59,7 @@ class APIs
/** /**
Download the config from the cloud. Download the config from the cloud.
## Parameters (Besides from username and password) ## Parameters
None None
## Returns ## Returns
@@ -97,88 +67,24 @@ class APIs
*/ */
static let downloadConfig = API<String>(loc: "/backup/download") static let downloadConfig = API<String>(loc: "/backup/download")
/**
Create a family
## Parameters (Besides from username and password)
- fname: Family name
- pin: Admin pin
## Returns
Family object
*/
static let familyCreate = API<Family>(loc: "/family/create")
/**
Delete a family
## Parameters (Besides from username and password)
- fid: Family ID
- pin: Admin pin
## Returns
Success or not
*/
static let familyDelete = API<String>(loc: "/family/delete")
/**
Change a family's admin pin
## Parameters (Besides from username and password)
- fid: Family ID
- orig_pin: Original admin pin
- new_pin: New admin pin
## Returns
Success or not
*/
static let familyChangePin = API<String>(loc: "/family/update_pin")
/**
Join family
## Parameters (Besides from username and password)
- fid: Family ID
- pin: Admin pin
## Returns
Family object
*/
static let familyJoin = API<Family>(loc: "/family/join")
/**
Leave family
## Parameters (Besides from username and password)
- fid: Family ID
- pin: Admin pin
## Returns
Success or not
*/
static let familyLeave = API<String>(loc: "/family/leave")
private init() {} private init() {}
} }
/** /**
Build a URLRequest with the node path and params Build a URL with the node path and params
- Parameter api: API Node (Eg. APIs.register) - Parameter api: API Node (Eg. APIs.register)
- Parameter params: Parameters to send to the server (Check the documentation of the API node to see which parameters you need) - Parameter params: Parameters to send to the server (Check the documentation of the API node to see which parameters you need)
- Returns: URLRequest - Returns: URL
*/ */
func createRequest(_ node: String, _ params: [String: String]? = [:]) -> URLRequest func createUrl(_ node: String, _ params: [String: String]? = [:]) -> URL
{ {
// Create URL and request var url = URLComponents(string: baseUrl + node)
let url = URLComponents(string: baseUrl + node) if let params = params
var request = URLRequest(url: url!.url!) {
request.httpMethod = "POST" url?.queryItems = params.map { URLQueryItem(name: $0, value: $1) }
}
// Put parameters inside headers return url!.url!
params?.forEach { request.setValue($1, forHTTPHeaderField: $0) }
return request
} }
/** /**
@@ -195,12 +101,14 @@ func send<T: Decodable>(_ api: API<T>, _ params: [String: String]? = [:], _ succ
var params = params var params = params
if params != nil if params != nil
{ {
if params!["username"] == nil { params!["username"] = localStorage.string(forKey: "name") } if params!["email"] == nil { params!["email"] = localStorage.string(forKey: "email") }
if params!["password"] == nil { params!["password"] = localStorage.string(forKey: "pass") } if params!["pass"] == nil { params!["pass"] = localStorage.string(forKey: "pass") }
} }
let url = createUrl(api.loc, params)
// Create task // Create task
let task = URLSession.shared.dataTask(with: createRequest(api.loc, params)) { (raw, response, error) in let task = URLSession.shared.dataTask(with: url) { (raw, response, error) in
// Check if raw data exists // Check if raw data exists
guard let response = response as? HTTPURLResponse, let raw = raw else { err("Data doesn't exist"); return } guard let response = response as? HTTPURLResponse, let raw = raw else { err("Data doesn't exist"); return }
@@ -208,11 +116,8 @@ func send<T: Decodable>(_ api: API<T>, _ params: [String: String]? = [:], _ succ
// If success // If success
if (200...299).contains(response.statusCode) if (200...299).contains(response.statusCode)
{ {
// If the desired type is string, it doesn't have to parse json.
if T.self == String.self, let msg = String(data: raw, encoding: .utf8) { success(msg as! T); return }
// Parse JSON // Parse JSON
guard let obj = try? JSON.decoder.decode(T.self, from: raw) else { err("JSON cannot be parsed"); return } guard let obj = try? JSON.decode(T.self, from: raw) else { err("JSON cannot be parsed"); return }
// Call callback // Call callback
success(obj) success(obj)
+11 -17
View File
@@ -8,7 +8,16 @@
import Foundation import Foundation
import CoreMotion import CoreMotion
import UserNotifications import UserNotifications
import UIKit
let motionManager = CMMotionManager()
func getAccelerometer() {
motionManager.startAccelerometerUpdates()
//print(motionManager.accelerometerData)
if let accelerometerData = motionManager.accelerometerData {
print("Acclerometer: \(accelerometerData)")
}
}
func walkAction() { func walkAction() {
@@ -18,25 +27,10 @@ func jumpAction() {
} }
func rpsAction(choice: RPS.Choice) -> Bool? { func puzzleAction() {
let rps = RPS()
return rps.playRPS(you: choice, computer: RPS.randomComputerChoice())
} }
// Handles the core logic behind the factoring alarm
func factorAction(puzzleQuestionLabel: UILabel) -> [Int] {
let problem = QuadraticProb()
let answer = problem.getAnswer()
let problemString = problem.getProblem()
puzzleQuestionLabel.text = "Solve: \(problemString)"
print("Answer: \(answer)")
return answer
}
func smashAction() { func smashAction() {
} }
+52
View File
@@ -0,0 +1,52 @@
//
// SceneDelegate.swift
// ProjectClock
//
// Created by Hykilpikonna on 1/6/21.
//
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
-129
View File
@@ -1,129 +0,0 @@
//
// StopwatchViewController.swift
// ProjectClock
//
// Created by Dallon Archibald on 1/23/21.
// Reference: https://youtu.be/H691qFRpaWA
import UIKit
class StopwatchViewController: UIViewController {
@IBOutlet weak var hourLabel: UILabel!
@IBOutlet weak var minuteLabel: UILabel!
@IBOutlet weak var secondLabel: UILabel!
@IBOutlet weak var startButton: UIButton!
@IBOutlet weak var stopButton: UIButton!
@IBOutlet weak var resetButton: UIButton!
@IBOutlet weak var lapButton: UIButton!
@IBOutlet weak var tableView: UITableView!
var hours = 0
var minutes = 0
var seconds = 0
var lappedTimes: [String] = []
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
//lapButton.isHidden = true
}
@IBAction func start(_ sender: UIButton) {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(count), userInfo: nil, repeats: true)
//startButton.isHidden = true
//lapButton.isHidden = false
}
@objc fileprivate func count() {
seconds += 1
if seconds == 60 {
minutes += 1
seconds = 0
}
if minutes == 60 {
hours += 1
minutes = 0
}
if hours == 24 {
resetTimes()
}
if seconds >= 10 { secondLabel.text = "\(seconds)" }
else { secondLabel.text = "0\(seconds)" }
if minutes >= 10 { minuteLabel.text = "\(minutes)" }
else { minuteLabel.text = "0\(minutes)" }
if hours >= 10 { hourLabel.text = "\(hours)" }
else { hourLabel.text = "0\(hours)" }
}
@IBAction func stop(_ sender: UIButton) {
timer.invalidate()
//startButton.isHidden = false
}
@IBAction func reset(_ sender: UIButton) {
resetTimes()
}
func resetTimes() {
seconds = 0
minutes = 0
seconds = 0
lappedTimes = []
timer.invalidate()
secondLabel.text = "00"
minuteLabel.text = "00"
hourLabel.text = "00"
tableView.reloadData()
//startButton.isHidden = false
//lapButton.isHidden = true
}
@IBAction func lap(_ sender: UIButton) {
var currentSec = ""
if seconds >= 10 { currentSec = "\(seconds)" }
else { currentSec = "0\(seconds)" }
var currentMin = ""
if minutes >= 10 { currentMin = "\(minutes)" }
else { currentMin = "0\(minutes)" }
var currentHour = ""
if hours >= 10 { currentHour = "\(hours)" }
else { currentHour = "0\(hours)" }
let currentTime = "\(currentHour):\(currentMin):\(currentSec)" //CHECK THIS
lappedTimes.append(currentTime)
let indexPath = IndexPath(row: lappedTimes.count - 1, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
}
extension StopwatchViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lappedTimes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "lapCell", for: indexPath)
cell.textLabel?.text = lappedTimes[indexPath.row]
cell.selectionStyle = .none
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
lappedTimes.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
}
+29 -24
View File
@@ -8,26 +8,30 @@
import UIKit import UIKit
import UserNotifications import UserNotifications
class TestingViewController: UIViewController class TestingViewController: UIViewController {
{
override func viewDidLoad() override func viewDidLoad() {
{
super.viewDidLoad() super.viewDidLoad()
// Request notification permission UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in (granted, error) in
if success { if granted {
print("All set!") print("Authorized Notifications")
} else if let error = error { } else {
print(error.localizedDescription) print("Error: No notification access")
} }
} }
// Do any additional setup after loading the view.
}
@IBAction func getAccel(_ sender: Any) {
getAccelerometer()
} }
//Sends a test notification //Sends a test notification
@IBAction func sendNotification(_ sender: Any) @IBAction func sendNotification(_ sender: Any) {
{ let alarm = Alarm(alarmTime: Date(), text: "Good morning!", wakeMethod: WVM(name: "walking", desc: "Walk"))
let alarm = Alarm(hour: 7, minute: 20, text: "Good morning!", wakeMethod: WVM(name: "walking", desc: "Walk"))
let content = UNMutableNotificationContent() let content = UNMutableNotificationContent()
@@ -47,6 +51,7 @@ class TestingViewController: UIViewController
let attachment = try! UNNotificationAttachment(identifier: imageName, url: imageURL, options: .none) let attachment = try! UNNotificationAttachment(identifier: imageName, url: imageURL, options: .none)
content.attachments = [attachment] content.attachments = [attachment]
// Readies notification to be sent // Readies notification to be sent
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false) let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
let request = UNNotificationRequest(identifier: "notification.id.01", content: content, trigger: trigger) let request = UNNotificationRequest(identifier: "notification.id.01", content: content, trigger: trigger)
@@ -55,14 +60,14 @@ class TestingViewController: UIViewController
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
} }
@IBAction func addAlarm(_ sender: Any) /*
{ // MARK: - Navigation
let (h, m, _) = Date().getHMS()
Alarms.fromLocal().apply { $0.list.append(Alarm(hour: h, minute: m, text: "Test alarm - \(h * m)", wakeMethod: wvms[1], repeats: [true, true, true, true, true, true, true], oneTime: true, lastActivate: Date().added(.minute, -1))) }.localSave() // In a storyboard-based application, you will often want to do a little preparation before navigation
} override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
@IBAction func deleteAlarm(_ sender: Any) // Pass the selected object to the new view controller.
{
Alarms.fromLocal().apply { $0.list.removeAll() }.localSave()
} }
*/
} }
-211
View File
@@ -1,211 +0,0 @@
//
// Utils.swift
// ProjectClock
//
// Created by Hykilpikonna on 1/17/21.
//
import Foundation
import CryptoKit
import UIKit
/// Date manipulations
extension Date
{
/// Add toString to Date
func str() -> String
{
let f = DateFormatter()
f.dateFormat = "yyyy-MM-dd hh:mm:ss"
return f.string(from: self)
}
/// Constructor from components
static func create(_ year: Int, _ month: Int, _ day: Int, _ hour: Int, _ minute: Int) -> Date
{
var c = DateComponents()
c.year = year
c.month = month
c.day = day
c.hour = hour
c.minute = minute
let cal = Calendar(identifier: .gregorian)
return cal.date(from: c)!
}
/// Get year, month, day
func getYMD() -> (y: Int, m: Int, d: Int)
{
let calendar = Calendar.current
let comp = calendar.dateComponents([.year, .month, .day], from: self)
return (comp.year!, comp.month!, comp.day!)
}
/// Get hour, minute, seconds
func getHMS() -> (h: Int, m: Int, s: Int)
{
let calendar = Calendar.current
let comp = calendar.dateComponents([.hour, .minute, .second], from: self)
return (comp.hour!, comp.minute!, comp.second!)
}
/// Get another component
func get(_ c: Calendar.Component) -> Int
{
let calendar = Calendar.current
let comp = calendar.dateComponents([c], from: self)
return comp.value(for: c)!
}
/// Return a new modified date
func added(_ c: Calendar.Component, _ v: Int) -> Date
{
return Calendar.current.date(byAdding: c, value: v, to: self)!
}
}
extension TimeInterval
{
var seconds: Int { return Int(self) % 60 }
var minutes: Int { return (Int(self) / 60) % 60 }
var hours: Int { return (Int(self) / 3600) % 24 }
var days: Int { return Int(self) / (3600 * 24) }
/// Add toString to time interval
func str() -> String
{
if days != 0 { return "\(days)d \(hours)h \(minutes)m \(seconds)s" }
else if hours != 0 { return "\(hours)h \(minutes)m \(seconds)s" }
else if minutes != 0 { return "\(minutes)m \(seconds)s" }
else { return "\(seconds)s" }
}
}
/// Apply like Kotlin
protocol HasApply {}
extension HasApply
{
@discardableResult
func apply(_ c: (Self) -> ()) -> Self
{
c(self)
return self
}
}
extension Alarm: HasApply {}
extension Alarms: HasApply {}
/// Hashing
extension Digest
{
var bytes: [UInt8] { Array(makeIterator()) }
var b64: String { Data(bytes).base64EncodedString() }
}
extension String
{
var sha256: String { SHA256.hash(data: self.data(using: .utf8)!).b64 }
}
/// UI Extensions
extension UIViewController
{
/**
Send an alert
- Parameter title: Title of the alert
- Parameter message: Body message of the alert
- Parameter okayable: Whether the alert can be okayed
*/
@discardableResult
func alert(_ title: String, _ message: String, okayable: Bool = false) -> UIAlertController
{
// Create alert
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
// Add okay button if it's okayable
if okayable { alert.addAction(UIAlertAction(title: "OK", style: .default)) }
// Display alert
self.present(alert, animated: true, completion: nil)
return alert
}
/// A message is an okayable alert
@discardableResult
func msg(_ title: String, _ message: String) -> UIAlertController { alert(title, message, okayable: true) }
/// More convenient dismiss function
func dismiss(_ completion: (() -> Void)? = nil) { ui { self.dismiss(animated: false, completion: completion) } }
/**
Send a http request even more conveniently
*/
func sendReq<T: Decodable>(_ api: API<T>, title: String, errors: [String: String] = [:], params: [String: String]? = [:], _ success: @escaping (T) -> Void, err: @escaping (String) -> Void = {it in})
{
// Send request
let a = alert(title, "Please Wait")
send(api, params) { it in a.dismiss { success(it) } }
err:
{
// Display error message
print("===== Error: \($0) =====")
let message = errors[$0.trimmingCharacters(in: .whitespaces)]
?? "Maybe the server is on fire, just wait a few hours."
a.dismiss { self.msg("An error occurred", message) }
}
}
}
/// Regex Matching (Credit: https://www.hackingwithswift.com/articles/108/how-to-use-regular-expressions-in-swift)
extension NSRegularExpression
{
convenience init(_ pattern: String)
{
do { try self.init(pattern: pattern) }
catch { preconditionFailure("Illegal regular expression: \(pattern).") }
}
func matches(_ string: String) -> Bool
{
let range = NSRange(location: 0, length: string.utf16.count)
return firstMatch(in: string, options: [], range: range) != nil
}
}
extension String
{
static func ~= (lhs: String, rhs: String) -> Bool
{
guard let regex = try? NSRegularExpression(pattern: rhs) else { return false }
let range = NSRange(location: 0, length: lhs.utf16.count)
return regex.firstMatch(in: lhs, options: [], range: range) != nil
}
}
/// More convenient ui update closure
func ui(closure: @escaping () -> Void) { DispatchQueue.main.async { closure() } }
/// More convenient UserDefaults access (Credit: https://gist.github.com/Otbivnoe/04b8bd7984fba0cb58ca7f136fd95582)
extension UserDefaults
{
subscript<T>(key: String) -> T?
{
get { return value(forKey: key) as? T }
set { set(newValue, forKey: key) }
}
subscript<T: RawRepresentable>(key: String) -> T?
{
get
{
if let rawValue = value(forKey: key) as? T.RawValue { return T(rawValue: rawValue) }
return nil
}
set { self[key] = newValue?.rawValue }
}
}