Skip to content

Commit

Permalink
mobile user permission for push notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
voynow committed Oct 30, 2024
1 parent c4c80f0 commit 0e04827
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 9 deletions.
7 changes: 5 additions & 2 deletions mobile/mobile.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
LoadingView.swift,
mobileApp.swift,
Models.swift,
NotificationManager.swift,
OnboardingView.swift,
PreferencesView.swift,
"Preview Content/Preview Assets.xcassets",
Expand Down Expand Up @@ -264,6 +265,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = mobile/mobile.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"mobile/Preview Content\"";
Expand All @@ -280,7 +282,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = voynow.mobile;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand All @@ -294,6 +296,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = mobile/mobile.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"mobile/Preview Content\"";
Expand All @@ -310,7 +313,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = voynow.mobile;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand Down
26 changes: 22 additions & 4 deletions mobile/mobile/APIManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,27 @@ class APIManager {
}
}
}
}

struct GenericResponse: Codable {
let success: Bool
let message: String?
func updateDeviceToken(token: String, deviceToken: String, completion: @escaping (Result<Void, Error>) -> Void) {
let body: [String: Any] = [
"jwt_token": token,
"method": "update_device_token",
"device_token": deviceToken
]

performRequest(body: body, responseType: GenericResponse.self) { result in
switch result {
case .success(let response):
if response.success {
completion(.success(()))
} else {
let errorMessage = response.message ?? "Failed to update device token"
completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: errorMessage])))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}

38 changes: 36 additions & 2 deletions mobile/mobile/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,51 @@
import UIKit
import os
import UserNotifications

class AppDelegate: UIResponder, UIApplicationDelegate {
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
private let logger = Logger(
subsystem: Bundle.main.bundleIdentifier ?? "com.trackflow", category: "AppDelegate")

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

logger.info(
"Application did finish launching with options: \(String(describing: launchOptions))")
return true
}

func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
NotificationManager.shared.updateDeviceToken(tokenString)
}

func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
logger.error("Failed to register for remote notifications: \(error.localizedDescription)")
}

private func registerForPushNotifications() {
UNUserNotificationCenter.current().delegate = self

UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .sound, .badge]
) { [weak self] granted, error in
guard let self = self else { return }

if granted {
self.logger.info("Notification permission granted")
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
} else if let error = error {
self.logger.error("Error requesting notification permission: \(error.localizedDescription)")
}
}
}
}
23 changes: 23 additions & 0 deletions mobile/mobile/AppState.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
import SwiftUI
import UserNotifications

class AppState: ObservableObject {
@Published var status: AppStateStatus = .loggedOut
@Published var jwtToken: String? = nil
@Published var notificationStatus: UNAuthorizationStatus = .notDetermined

func checkNotificationStatus() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
DispatchQueue.main.async {
self.notificationStatus = settings.authorizationStatus
}
}
}

func requestNotificationPermission() {
UNUserNotificationCenter.current().delegate = UIApplication.shared.delegate as? UNUserNotificationCenterDelegate

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
DispatchQueue.main.async {
self.checkNotificationStatus()
if granted {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
}
}
7 changes: 6 additions & 1 deletion mobile/mobile/DashboardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ struct DashboardView: View {
}
}
}
.onAppear(perform: fetchData)
.onAppear {
fetchData()
if appState.notificationStatus == .notDetermined {
appState.requestNotificationPermission()
}
}
.alert(isPresented: $showErrorAlert) {
Alert(
title: Text("Error"),
Expand Down
4 changes: 4 additions & 0 deletions mobile/mobile/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,9 @@
<array>
<string>strava</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
</dict>
</plist>
5 changes: 5 additions & 0 deletions mobile/mobile/Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,8 @@ enum AppStateStatus {
case loggedIn
case newUser
}

struct GenericResponse: Codable {
let success: Bool
let message: String?
}
34 changes: 34 additions & 0 deletions mobile/mobile/NotificationManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Foundation
import os

class NotificationManager {
static let shared = NotificationManager()
private var deviceToken: String?
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.trackflow", category: "NotificationManager")

private init() {}

func updateDeviceToken(_ token: String) {
deviceToken = token
logger.info("Received new device token")
sendTokenToServer()
}

private func sendTokenToServer() {
guard let token = deviceToken,
let jwtToken = UserDefaults.standard.string(forKey: "jwt_token")
else {
logger.error("Missing device token or JWT token")
return
}

APIManager.shared.updateDeviceToken(token: jwtToken, deviceToken: token) { result in
switch result {
case .success:
self.logger.info("Successfully registered device token with server")
case .failure(let error):
self.logger.error("Failed to register device token: \(error.localizedDescription)")
}
}
}
}
8 changes: 8 additions & 0 deletions mobile/mobile/mobile.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +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>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>
1 change: 1 addition & 0 deletions mobile/mobile/mobileApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import SwiftUI

@main
struct mobileApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject private var appState = AppState()

var body: some Scene {
Expand Down

0 comments on commit 0e04827

Please sign in to comment.