diff --git a/myAlarm.xcodeproj/project.pbxproj b/myAlarm.xcodeproj/project.pbxproj index a0d55d8..e17d90c 100644 --- a/myAlarm.xcodeproj/project.pbxproj +++ b/myAlarm.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 5627CB4724CF681800917FD0 /* AlarmListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5627CB4624CF681800917FD0 /* AlarmListTableViewController.swift */; }; + 5627CB4924CF694600917FD0 /* AlarmDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5627CB4824CF694600917FD0 /* AlarmDetailTableViewController.swift */; }; + 5627CB4B24CF69B500917FD0 /* SwitchDetailTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5627CB4A24CF69B500917FD0 /* SwitchDetailTableViewCell.swift */; }; + 5627CB4D24CF715600917FD0 /* Alarm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5627CB4C24CF715600917FD0 /* Alarm.swift */; }; + 5627CB4F24CF7DAF00917FD0 /* AlarmController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5627CB4E24CF7DAF00917FD0 /* AlarmController.swift */; }; F7C2038C2131AD3400C3FC77 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C2038B2131AD3400C3FC77 /* AppDelegate.swift */; }; F7C203912131AD3400C3FC77 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7C2038F2131AD3400C3FC77 /* Main.storyboard */; }; F7C203932131AD3400C3FC77 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F7C203922131AD3400C3FC77 /* Assets.xcassets */; }; @@ -14,6 +19,11 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 5627CB4624CF681800917FD0 /* AlarmListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AlarmListTableViewController.swift; path = "Settings/Settings/Settings/Views/Custom Views/AlarmListTableViewController.swift"; sourceTree = SOURCE_ROOT; }; + 5627CB4824CF694600917FD0 /* AlarmDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AlarmDetailTableViewController.swift; path = "Settings/Settings/Settings/Views/Custom Views/AlarmDetailTableViewController.swift"; sourceTree = SOURCE_ROOT; }; + 5627CB4A24CF69B500917FD0 /* SwitchDetailTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchDetailTableViewCell.swift; sourceTree = ""; }; + 5627CB4C24CF715600917FD0 /* Alarm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Alarm.swift; path = myAlarm/Resources/Alarm.swift; sourceTree = SOURCE_ROOT; }; + 5627CB4E24CF7DAF00917FD0 /* AlarmController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AlarmController.swift; path = myAlarm/Resources/AlarmController.swift; sourceTree = SOURCE_ROOT; }; F7C203882131AD3400C3FC77 /* myAlarm.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = myAlarm.app; sourceTree = BUILT_PRODUCTS_DIR; }; F7C2038B2131AD3400C3FC77 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; F7C203902131AD3400C3FC77 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -63,6 +73,7 @@ F7C203A72131B1D500C3FC77 /* Models */ = { isa = PBXGroup; children = ( + 5627CB4C24CF715600917FD0 /* Alarm.swift */, ); path = Models; sourceTree = ""; @@ -70,6 +81,9 @@ F7C203A82131B1DB00C3FC77 /* Controllers */ = { isa = PBXGroup; children = ( + 5627CB4624CF681800917FD0 /* AlarmListTableViewController.swift */, + 5627CB4824CF694600917FD0 /* AlarmDetailTableViewController.swift */, + 5627CB4E24CF7DAF00917FD0 /* AlarmController.swift */, ); path = Controllers; sourceTree = ""; @@ -77,6 +91,7 @@ F7C203A92131B1E300C3FC77 /* Views */ = { isa = PBXGroup; children = ( + 5627CB4A24CF69B500917FD0 /* SwitchDetailTableViewCell.swift */, ); path = Views; sourceTree = ""; @@ -164,7 +179,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5627CB4924CF694600917FD0 /* AlarmDetailTableViewController.swift in Sources */, F7C2038C2131AD3400C3FC77 /* AppDelegate.swift in Sources */, + 5627CB4D24CF715600917FD0 /* Alarm.swift in Sources */, + 5627CB4B24CF69B500917FD0 /* SwitchDetailTableViewCell.swift in Sources */, + 5627CB4724CF681800917FD0 /* AlarmListTableViewController.swift in Sources */, + 5627CB4F24CF7DAF00917FD0 /* AlarmController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/myAlarm/Resources/Alarm.swift b/myAlarm/Resources/Alarm.swift new file mode 100644 index 0000000..dc51268 --- /dev/null +++ b/myAlarm/Resources/Alarm.swift @@ -0,0 +1,41 @@ +// +// Alarm.swift +// myAlarm +// +// Created by Drew Miller on 7/27/20. +// Copyright © 2020 trevorAdcock. All rights reserved. +// + +import Foundation + +class Alarm: Codable { + + var fireDate: Date + var name: String + var enabled: Bool + var uuid: String + + init(fireDate: Date, name: String, enabled: Bool, uuid: String = UUID().uuidString) { + self.fireDate = fireDate + self.name = name + self.enabled = enabled + self.uuid = uuid + } + + var fireTimeAsString: String { + + let date = DateFormatter() + + date.timeStyle = .short + + return date.string(from: fireDate) + + } +}//end of class + +extension Alarm: Equatable { + static func == (lhs: Alarm, rhs: Alarm) -> Bool { + lhs.fireDate == rhs.fireDate && lhs.name == rhs.name && lhs.enabled == rhs.enabled + && lhs.uuid == rhs.uuid + } +}//end of extension diff --git a/myAlarm/Resources/AlarmController.swift b/myAlarm/Resources/AlarmController.swift new file mode 100644 index 0000000..87cc806 --- /dev/null +++ b/myAlarm/Resources/AlarmController.swift @@ -0,0 +1,123 @@ +// +// AlarmController.swift +// myAlarm +// +// Created by Drew Miller on 7/27/20. +// Copyright © 2020 trevorAdcock. All rights reserved. +// + +import Foundation +import UserNotifications + +protocol AlarmScheduler: class { + func cancelUserNotifications(for alarm: Alarm) + func scheduleUserNotifications(for alarm: Alarm) +} + +class AlarmController: AlarmScheduler { + + init() { + loadFromPersistentStore() + } + + //Source of Truth + static let shared = AlarmController() + + var alarms: [Alarm] = [] + + func addAlarm(fireDate: Date, name: String, isEnabled: Bool) { + let newAlarm = Alarm(fireDate: fireDate, name: name, enabled: isEnabled) + alarms.append(newAlarm) + if newAlarm.enabled { + scheduleUserNotifications(for: newAlarm) + } else { + cancelUserNotifications(for: newAlarm) + } + saveToPersistentStore() + } + + func update(alarm: Alarm, fireDate: Date, name: String, isEnabled: Bool) { + alarm.fireDate = fireDate + alarm.name = name + alarm.enabled = isEnabled + if alarm.enabled { + scheduleUserNotifications(for: alarm) + } else { + cancelUserNotifications(for: alarm) + } + saveToPersistentStore() + } + + func delete(alarm: Alarm) { + if let index = self.alarms.firstIndex(where: {$0 == alarm}) { + self.alarms.remove(at: index) + } + cancelUserNotifications(for: alarm) + saveToPersistentStore() + } + + func toggleEnabled(for alarm: Alarm) { + alarm.enabled = !alarm.enabled + if alarm.enabled == false { + scheduleUserNotifications(for: alarm) + } else { + cancelUserNotifications(for: alarm) + } + } + + func fileURL() -> URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + let documentDirectory = paths[0] + let fileName = "Alarm.json" + let url = documentDirectory.appendingPathComponent(fileName) + return url + } + + func saveToPersistentStore() { + let jsonEncoder = JSONEncoder() + + do { + let data = try jsonEncoder.encode(AlarmController.shared.alarms) + try data.write(to: fileURL()) + } catch let error { + print("Error saving to persistent store: \(error.localizedDescription)") + } + } + + func loadFromPersistentStore() { + let jsonDecoder = JSONDecoder() + + do { + let data = try Data(contentsOf: fileURL()) + let decodedAlarm = try jsonDecoder.decode([Alarm].self, from: data) + self.alarms = decodedAlarm + } catch let error { + print("Error loading from persistent store: \(error.localizedDescription)") + } + } +} + +extension AlarmScheduler { + func cancelUserNotifications(for alarm: Alarm) { + UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [alarm.uuid]) + } + + func scheduleUserNotifications(for alarm: Alarm) { + let notificationContent = UNMutableNotificationContent() + notificationContent.title = "Alarm" + notificationContent.body = "Your alarm is ringing" + + + let date = alarm.fireDate + let dateComponents = Calendar.current.dateComponents([.hour, .minute], from: date) + let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) + + let request = UNNotificationRequest(identifier: alarm.uuid, content: notificationContent, trigger: trigger) + + UNUserNotificationCenter.current().add(request) { (error) in + if let error = error { + print(error) + } + } + } +} diff --git a/myAlarm/Resources/AppDelegate.swift b/myAlarm/Resources/AppDelegate.swift index 16f74ef..2fe6928 100644 --- a/myAlarm/Resources/AppDelegate.swift +++ b/myAlarm/Resources/AppDelegate.swift @@ -18,7 +18,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - AlarmController.shared.alarms = AlarmController.shared.loadFromPersisentStore() + // AlarmController.shared.alarms = AlarmController.shared.loadFromPersisentStore() UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (accepted, error) in if !accepted{ diff --git a/myAlarm/Resources/Base.lproj/Main.storyboard b/myAlarm/Resources/Base.lproj/Main.storyboard index c416b21..be753ca 100644 --- a/myAlarm/Resources/Base.lproj/Main.storyboard +++ b/myAlarm/Resources/Base.lproj/Main.storyboard @@ -1,11 +1,216 @@ - - - - + + - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/myAlarm/Views/SwitchDetailTableViewCell.swift b/myAlarm/Views/SwitchDetailTableViewCell.swift new file mode 100644 index 0000000..e37a97e --- /dev/null +++ b/myAlarm/Views/SwitchDetailTableViewCell.swift @@ -0,0 +1,56 @@ +// +// SwitchDetailTableViewCell.swift +// myAlarm +// +// Created by Drew Miller on 7/27/20. +// Copyright © 2020 trevorAdcock. All rights reserved. +// + +import UIKit + +protocol SwitchTableViewCellDelegate: class { + + func switchCellSwitchValueChanged(cell: SwitchDetailTableViewCell) +} + +class SwitchDetailTableViewCell: UITableViewCell { + + //Landing Pad + var alarm: Alarm? { + didSet { + updateViews() + } + } + + weak var delegate: SwitchTableViewCellDelegate? + + + @IBOutlet weak var timeLabel: UILabel! + @IBOutlet weak var nameLabel: UILabel! + @IBOutlet weak var alarmSwitch: UISwitch! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + + //MARK: - Action + @IBAction func switchValueChanged(_ sender: Any) { + delegate?.switchCellSwitchValueChanged(cell: self) + } + + + func updateViews() { + guard let alarm = alarm else { return } + timeLabel.text = alarm.fireTimeAsString + nameLabel.text = alarm.name + alarmSwitch.isOn = alarm.enabled + } + +}