Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/harsh jenny/push notifications #171

Merged
merged 29 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ed54245
push notifications via firebase
jennymar May 12, 2023
6283623
Merge branch 'main' into feature/Jenny/PushNotifications
jennymar May 17, 2023
82514b8
Merge branch 'main' into feature/Jenny/PushNotifications
HarshGurnani May 20, 2023
655055b
started sendNotification function
HarshGurnani May 21, 2023
016f096
minor formatting issues + added fcm token to current user modal
HarshGurnani May 23, 2023
b622172
added patch request to send fcm token to backend
HarshGurnani May 23, 2023
6d396df
set up frontend to send fcm token
HarshGurnani May 23, 2023
2e20b39
set up notification sending when a session is made
HarshGurnani May 25, 2023
e8c806b
Merge branch 'main' into feature/HarshJenny/PushNotifications
HarshGurnani May 27, 2023
fd7a593
worked on cron job stuff
HarshGurnani May 27, 2023
1c6cdb9
sessions flag for upcoming and post sessions
jennymar May 27, 2023
28ab0e9
upcoming notif cron job (has bugs)
jennymar May 29, 2023
9f1e60e
set up cron jobs in a separate function
HarshGurnani May 30, 2023
065ac5d
just need to fix setting the flag to true
HarshGurnani May 31, 2023
7eb2f53
Merge branch 'main' into feature/HarshJenny/PushNotifications
HarshGurnani May 31, 2023
2e5a716
finished upcoming notification sending
HarshGurnani May 31, 2023
bbe452a
set up notifications for after session is completed
HarshGurnani Jun 1, 2023
ae3c3c8
Merge branch 'main' into feature/HarshJenny/PushNotifications
HarshGurnani Jun 1, 2023
9d449cc
minor changes with error handling
HarshGurnani Jun 1, 2023
fc033bf
updated fcm token route + added notification instances for deleted se…
HarshGurnani Jun 2, 2023
58cb304
Merge branch 'main' into feature/HarshJenny/PushNotifications
HarshGurnani Jun 8, 2023
c66e246
temp commit for notification system - still need to work on this
HarshGurnani Oct 3, 2023
64a767a
Merge branch 'main' into feature/HarshJenny/PushNotifications
HarshGurnani Oct 16, 2023
6c95d61
sending FCM token to back end works
HarshGurnani Oct 20, 2023
84771c0
last bugs with fcm token
HarshGurnani Oct 20, 2023
87a3bc0
sending FCM token works
HarshGurnani Oct 27, 2023
36656e7
notification system working - next steps: make sure all users in the …
HarshGurnani Oct 27, 2023
be32c74
linting
HarshGurnani Oct 29, 2023
20dc5d1
fixed requested changes + linting
HarshGurnani Nov 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ALUM/ALUM.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@
0748208D29712921004AF547 /* ALUM */,
0748208C29712921004AF547 /* Products */,
0799ACEE2A0102E400EEAFA2 /* Recovered References */,
2A06083E2A0E3128007F38D6 /* Frameworks */,
);
sourceTree = "<group>";
};
Expand Down Expand Up @@ -537,6 +538,13 @@
path = Services;
sourceTree = "<group>";
};
2A06083E2A0E3128007F38D6 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
97992B5D29A6E7E200701CC7 /* SignUpPage */ = {
isa = PBXGroup;
children = (
Expand Down
5 changes: 4 additions & 1 deletion ALUM/ALUM/ALUM.entitlements
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
<dict>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>
98 changes: 90 additions & 8 deletions ALUM/ALUM/ALUMApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@ import SwiftUI
import FirebaseCore
import FirebaseFirestore
import FirebaseAuth

class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
import Firebase
import UserNotifications
import FirebaseMessaging

extension View {
func dismissKeyboardOnDrag() -> some View {
Expand All @@ -38,3 +33,90 @@ struct ALUMApp: App {
}
}
}

class AppDelegate: NSObject, UIApplicationDelegate {
let gcmMessageIDKey = "gcm.message_id"
@ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared

func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
FirebaseApp.configure()

Messaging.messaging().delegate = self

if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self

let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in }
)
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}

application.registerForRemoteNotifications()
return true
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}

print(userInfo)

completionHandler(UIBackgroundFetchResult.newData)
}
}

extension AppDelegate: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
currentUser.fcmToken = fcmToken
}
}

@available(iOS 10, *)
extension AppDelegate: UNUserNotificationCenterDelegate {

// Receive displayed notifications for iOS 10 devices.
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler:
@escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo

if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}

print(userInfo)

// Change this to your preferred presentation option
completionHandler([[.banner, .badge, .sound]])
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}

func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo

if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID from userNotificationCenter didReceive: \(messageID)")
}

print(userInfo)

completionHandler()
}
}
7 changes: 7 additions & 0 deletions ALUM/ALUM/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,12 @@
<string>Metropolis-ExtraBoldItalic.otf</string>
<string>Metropolis-Thin.otf</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
</dict>
</plist>
34 changes: 34 additions & 0 deletions ALUM/ALUM/Models/CurrentUserModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ enum UserRole {
case mentee
}

struct FCMToken: Codable {
var fcmToken: String
}

class CurrentUserModel: ObservableObject {
static let shared = CurrentUserModel()

@Published var fcmToken: String?
@Published var isLoading: Bool
@Published var uid: String?
@Published var role: UserRole?
Expand All @@ -31,6 +36,7 @@ class CurrentUserModel: ObservableObject {
@Published var pairedMenteeId: String?

init() {
self.fcmToken = nil
self.isLoading = true
self.isLoggedIn = false
self.uid = nil
Expand Down Expand Up @@ -61,6 +67,7 @@ class CurrentUserModel: ObservableObject {
self.setCurrentUser(isLoading: false, isLoggedIn: false, uid: nil, role: nil)
} else {
try await self.setFromFirebaseUser(user: user!)
try await sendFcmToken(fcmToken: fcmToken!)
}
} catch {
// in case setFromFirebaseUser fails, just make the user login again
Expand Down Expand Up @@ -153,4 +160,31 @@ class CurrentUserModel: ObservableObject {
}
return userStatus
}

func sendFcmTokenHelper(fcmToken: String) {
Task {
do {
try await sendFcmToken(fcmToken: fcmToken)
} catch {
print("Error in sending FCM Token")
}
}
}

func sendFcmToken(fcmToken: String) async throws {
print(fcmToken)
print(self.uid)
var tokenToSend: FCMToken = FCMToken(fcmToken: fcmToken)
let route = APIRoute.patchUser(userId: self.uid ?? "")
var request = try await route.createURLRequest()
guard let jsonData = try? JSONEncoder().encode(tokenToSend) else {
DispatchQueue.main.async {
CurrentUserModel.shared.showInternalError.toggle()
}
throw AppError.internalError(.jsonParsingError, message: "Failed to Encode Data")
}
request.httpBody = jsonData
let responseData = try await ServiceHelper.shared.sendRequestWithSafety(route: route, request: request)
print("SUCCESS - \(route.label)")
}
}
137 changes: 74 additions & 63 deletions ALUM/ALUM/Services/APIConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ enum APIRoute {
case getMentee(userId: String)
case postMentor
case postMentee
case patchUser(userId: String)
case getCalendly

case getNote(noteId: String)
Expand Down Expand Up @@ -65,87 +66,97 @@ enum APIRoute {
return URLString.sessions
case .postSession:
return URLString.sessions
case .getCalendly:
return URLString.calendly
case .patchUser(let userId):
return [URLString.user, userId].joined(separator: "/")
case .deleteSession(sessionId: let sessionId):
return [URLString.sessions, sessionId].joined(separator: "/")
case .patchSession(sessionId: let sessionId):
return [URLString.sessions, sessionId].joined(separator: "/")
case .getCalendly:
return URLString.calendly
}
}

var method: String {
switch self {
case .getSelf, .getMentee, .getMentor, .getNote, .getSession, .getSessions, .getCalendly:
return "GET"
case .postMentor, .postMentee, .postSession:
return "POST"
case .deleteSession:
return "DELETE"
case .patchNote, .patchSession:
return "PATCH"
}
var method: String {
switch self {
case .getSelf, .getMentee, .getMentor, .getNote, .getSession, .getSessions, .getCalendly:
return "GET"
case .postMentor, .postMentee, .postSession:
return "POST"
case .patchNote, .patchUser, .patchSession:
return "PATCH"
case .deleteSession:
return "DELETE"
}
}

var requireAuth: Bool {
switch self {
case .getSelf, .getMentor, .getMentee, .getNote, .patchNote, .getSession,
.getSessions, .postSession, .getCalendly, .deleteSession,
.patchSession:
return true
case .postMentee, .postMentor:
return false
}
var requireAuth: Bool {
switch self {
case
.getSelf,
.getMentor,
.getMentee,
.getNote,
.patchNote,
.getSession,
.getSessions,
.postSession,
.getCalendly,
.patchUser,
.patchSession,
.deleteSession:
return true
case .postMentee, .postMentor:
return false
}
}

func createURLRequest() async throws -> URLRequest {
return try await ServiceHelper.shared.createRequest(
urlString: self.url,
method: self.method,
requireAuth: self.requireAuth
)
}
func createURLRequest() async throws -> URLRequest {
return try await ServiceHelper.shared.createRequest(
urlString: self.url,
method: self.method,
requireAuth: self.requireAuth
)
}

var label: String {
return "\(self.method) \(self.url)"
}
var label: String {
return "\(self.method) \(self.url)"
}

var successCode: Int {
switch self {
case .getSelf, .getMentor, .getMentee, .getNote, .patchNote,
.getSession, .getSessions, .getCalendly,
.deleteSession, .patchSession:
return 200 // 200 Ok
case .postMentor, .postMentee, .postSession:
return 201 // 201 Created
}
var successCode: Int {
switch self {
case .getSelf, .getMentor, .getMentee, .getNote, .patchNote,
.getSession, .getSessions, .getCalendly, .patchUser, .deleteSession, .patchSession:
return 200 // 200 Ok
case .postMentor, .postMentee, .postSession:
return 201 // 201 Created
}
}

func getAppError(statusCode: Int, message: String) -> AppError {
let labeledMessage = "\(self.label) - \(message)"
let errorMap: [Int: AppError]
func getAppError(statusCode: Int, message: String) -> AppError {
let labeledMessage = "\(self.label) - \(message)"
let errorMap: [Int: AppError]

switch self {
case .getSelf, .getMentor, .getMentee, .getNote, .patchNote,
.getSession, .getSessions, .getCalendly,
.deleteSession, .patchSession:
errorMap = [
401: AppError.actionable(.authenticationError, message: labeledMessage),
400: AppError.internalError(.invalidRequest, message: labeledMessage),
404: AppError.internalError(.invalidRequest, message: labeledMessage)
]
case .postSession:
switch self {
case .getSelf, .getMentor, .getMentee, .getNote, .patchNote,
.getSession, .getSessions, .getCalendly, .patchUser,
.deleteSession, .patchSession:
errorMap = [
401: AppError.actionable(.authenticationError, message: labeledMessage),
400: AppError.internalError(.invalidRequest, message: labeledMessage),
404: AppError.internalError(.invalidRequest, message: labeledMessage)
]
case .postSession:
errorMap = [
400: AppError.internalError(.invalidRequest, message: labeledMessage)
]
case .postMentor, .postMentee:
// message will be displayed to user so no label here
errorMap = [
400: AppError.actionable(.invalidInput, message: message)
]
}

let error = errorMap[statusCode] ?? AppError.internalError(.unknownError, message: labeledMessage)
return error
case .postMentor, .postMentee:
errorMap = [
400: AppError.internalError(.invalidRequest, message: message)
]
}

let error = errorMap[statusCode] ?? AppError.internalError(.unknownError, message: labeledMessage)
return error
}
}
Loading
Loading