Skip to content

Commit 18b03c0

Browse files
authored
Merge pull request #23 from codeRIT/staging
v1.1
2 parents 4a50762 + 5549246 commit 18b03c0

6 files changed

Lines changed: 100 additions & 43 deletions

File tree

BrickHack-Mobile.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@
454454
"@executable_path/Frameworks",
455455
);
456456
PRODUCT_BUNDLE_IDENTIFIER = io.BrickHack.Mobile;
457-
MARKETING_VERSION = 1.0.1;
457+
MARKETING_VERSION = 1.1;
458458
PRODUCT_NAME = "BrickHack 6";
459459
PROVISIONING_PROFILE_SPECIFIER = "";
460460
SWIFT_VERSION = 4.2;
@@ -478,7 +478,7 @@
478478
"@executable_path/Frameworks",
479479
);
480480
PRODUCT_BUNDLE_IDENTIFIER = io.BrickHack.Mobile;
481-
MARKETING_VERSION = 1.0.1;
481+
MARKETING_VERSION = 1.1;
482482
PRODUCT_NAME = "BrickHack 6";
483483
PROVISIONING_PROFILE_SPECIFIER = "";
484484
SWIFT_VERSION = 4.2;

BrickHack-Mobile/AlertMessage.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class MessageHandler {
3737
iconText: "⚠️")
3838

3939
// Tapping message will hide view
40-
view.tapHandler = { _ in SwiftMessages.hide() }
40+
view.tapHandler = { _ in SwiftMessages.hide() }
4141

4242
// Configure view properties
4343
// @TODO: Add progress bar timer, à-la Discord?
@@ -47,9 +47,7 @@ class MessageHandler {
4747
config.preferredStatusBarStyle = .lightContent
4848
config.duration = .automatic
4949

50-
5150
SwiftMessages.show(config: config, view: view)
52-
5351
}
5452

5553
static func showConnectionError() {
@@ -124,4 +122,18 @@ class MessageHandler {
124122
body: "Please try again later, by restarting the app.",
125123
type: .error)
126124
}
125+
126+
static func showNotificationRegisterError(withEventTitle title: String) {
127+
print("ERROR: Unable to register notification for event \(title)")
128+
showAlertMessage(withTitle: "Unable to register a notification!",
129+
body: "Please try again later.",
130+
type: .error)
131+
}
132+
133+
static func showNotificationDisabledInfo() {
134+
print("INFO: Notification permissions denied, need to reset in Settings!")
135+
showAlertMessage(withTitle: "Notifications are disabled!",
136+
body: "Go to the Settings app to re-enable notifications.",
137+
type: .info)
138+
}
127139
}

BrickHack-Mobile/AppDelegate.swift

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
import UIKit
1010

1111
@UIApplicationMain
12-
class AppDelegate: UIResponder, UIApplicationDelegate {
12+
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
1313

1414
var window: UIWindow?
1515

1616
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17-
// Override point for customization after application launch.
17+
UNUserNotificationCenter.current().delegate = self
1818
return true
1919
}
2020

@@ -29,28 +29,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
2929
return false
3030
}
3131

32-
func applicationWillResignActive(_ application: UIApplication) {
33-
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
34-
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
35-
}
32+
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
3633

37-
func applicationDidEnterBackground(_ application: UIApplication) {
38-
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
39-
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
34+
print("WILL PRESENT \(notification)")
35+
completionHandler([.alert, .sound])
4036
}
4137

42-
func applicationWillEnterForeground(_ application: UIApplication) {
43-
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
44-
}
45-
46-
func applicationDidBecomeActive(_ application: UIApplication) {
47-
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
48-
}
49-
50-
func applicationWillTerminate(_ application: UIApplication) {
51-
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
52-
}
5338

5439

5540
}
56-

BrickHack-Mobile/Controllers/ScheduleTableViewController.swift

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
import UIKit
1010
import TimelineTableViewCell
1111
import SwiftMessages
12+
import UserNotifications
1213

13-
class ScheduleTableViewController: UITableViewController {
14+
class ScheduleTableViewController: UITableViewController, UNUserNotificationCenterDelegate {
1415

1516
// MARK: Ivars
1617
var scheduleTimer = Timer()
@@ -89,19 +90,14 @@ class ScheduleTableViewController: UITableViewController {
8990
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
9091

9192
let cell = tableView.dequeueReusableCell(withIdentifier: "TimelineTableViewCell", for: indexPath) as! TimelineTableViewCell
92-
93-
// Grab from our custom config
94-
// Description unused for now
95-
// let (timelinePoint, allColor, title, description, isFavorite, date)
96-
9793
let currentTimelineEvent = timelineEvents[convertIndex(fromIndexPath: indexPath)]
9894

9995
/*
10096
Overview of how cells are drawn
10197

10298
----------------
10399
backColor |
104-
tPoint.color o 12:30 (bold)
100+
tPoint.color o Title (bold)
105101
|
106102
frontColor | Description
107103
|
@@ -171,6 +167,8 @@ class ScheduleTableViewController: UITableViewController {
171167
}
172168

173169

170+
// MARK: Some helper functions
171+
174172
// Helper function to get a global index for an event from its local table index
175173
// Section > Row:
176174
// 0
@@ -214,21 +212,86 @@ class ScheduleTableViewController: UITableViewController {
214212
}
215213

216214
// Update view
217-
// (FavoriteButton subclass handles this condition)
215+
// (FavoriteButton subclass handles updating this condition)
218216
favButton.isSelected = !favButton.isSelected
219217

220-
// Update model
221-
// (We handle this condition!)
218+
// Now, prep the model:
219+
220+
// Get the event at this position
222221
let indexPath = IndexPath(row: favButton.row!, section: favButton.section!)
223222
let selectedEvent = timelineEvents[convertIndex(fromIndexPath: indexPath)]
223+
224+
// Update model
224225
selectedEvent.isFavorite = !selectedEvent.isFavorite
225-
print("user toggled \(selectedEvent.event.title)")
226+
227+
// Manage the list of events
228+
if selectedEvent.isFavorite {
229+
scheduleFavoriteNotification(forEvent: selectedEvent)
230+
print("Scheduled notification for \(selectedEvent.event.title)")
231+
} else {
232+
unscheduleFavoriteNotification(forEvent: selectedEvent)
233+
print("Unscheduled notification for \(selectedEvent.event.title)")
234+
}
226235

227236
// @TODO: Handle updating favorite with server
228-
// @TODO: Handle notifying users on their favorited events
229237
}
230238

239+
// MARK: Notifications
240+
private func scheduleFavoriteNotification(forEvent timelineEvent: TimelineEvent) {
241+
242+
askForNotificationPermissionIfNeeded()
243+
244+
// If this looks complicated, see this StackOverflow answer:
245+
// https://stackoverflow.com/a/60134234/1431900
246+
let now = Date(timeIntervalSinceNow: 0)
247+
248+
// If event has already occured, silently fail.
249+
if (timelineEvent.event.time < now) {
250+
return
251+
}
252+
253+
// Otherwise, let's calculate the time until the next event
254+
let interval = timelineEvent.event.time.timeIntervalSince(Date(timeIntervalSinceNow: 0))
255+
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: interval, repeats: false)
256+
257+
let content = UNMutableNotificationContent()
258+
content.title = timelineEvent.event.title + " is starting!"
259+
content.body = timelineEvent.event.description
260+
content.sound = .default
261+
let request = UNNotificationRequest(identifier: timelineEvent.event.uuid, content: content, trigger: trigger)
262+
263+
// Add request to local notification center
264+
UNUserNotificationCenter.current().add(request) { error in
265+
if error != nil {
266+
DispatchQueue.main.async {
267+
MessageHandler.showNotificationRegisterError(withEventTitle: timelineEvent.event.title)
268+
}
269+
return
270+
}
271+
}
272+
}
273+
274+
private func unscheduleFavoriteNotification(forEvent timelineEvent: TimelineEvent) {
275+
let identifier = timelineEvent.event.uuid
276+
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [identifier])
277+
}
278+
279+
private func askForNotificationPermissionIfNeeded() {
231280

281+
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { (granted, error) in
282+
283+
print("NOTIF ERROR: \(error)")
284+
if !granted {
285+
DispatchQueue.main.async {
286+
MessageHandler.showNotificationDisabledInfo()
287+
}
288+
} else {
289+
DispatchQueue.main.async {
290+
UIApplication.shared.registerForRemoteNotifications()
291+
}
292+
}
293+
}
294+
}
232295

233296
// MARK: Section headers and view configuration
234297

@@ -298,8 +361,6 @@ class ScheduleTableViewController: UITableViewController {
298361

299362
// Otherwise, go on to configure this current section as "passed"
300363
self.timelineEvents.filter({ $0.event.section == sectionIndex }).forEach { timelineEvent in
301-
302-
print("updated \(timelineEvent)")
303364
timelineEvent.allColor = self.backColor
304365
}
305366

BrickHack-Mobile/Models/ScheduleParser.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ class ScheduleParser {
202202

203203
// The parse loop
204204
// @FIXME: Using 3rd sheet as test, convert to 1st for production
205-
for (rowIndex, rowData) in data.sheets[0].data[0].rowData.enumerated() {
205+
for (rowIndex, rowData) in data.sheets[2].data[0].rowData.enumerated() {
206206

207207
// See comment below for how this skip variable functions
208208
var skip = false
@@ -307,7 +307,7 @@ class ScheduleParser {
307307
// Convert the spreadsheet time into a "blank" Date
308308
let dateFormatter = DateFormatter()
309309
dateFormatter.dateFormat = "hh:mma"
310-
dateFormatter.timeZone = TimeZone(identifier: "UTC")
310+
dateFormatter.timeZone = TimeZone(identifier: "America/New_York")
311311

312312
let convertedTime = dateFormatter.date(from: text)
313313

Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ EXTERNAL SOURCES:
4040

4141
CHECKOUT OPTIONS:
4242
TimelineTableViewCell:
43-
:commit: cb02fcce4a31978cf430db47066a6fbb6e276e90
43+
:commit: 47c8949274aa6161d9639f73bb08d0d306eb08e8
4444
:git: https://github.com/peterkos/TimelineTableViewCell.git
4545

4646
SPEC CHECKSUMS:

0 commit comments

Comments
 (0)