Skip to content

Instantly share code, notes, and snippets.

@gtokman
Last active July 8, 2025 21:41
Show Gist options
  • Save gtokman/dcede29516df6a41d57bfdb729bb0cb8 to your computer and use it in GitHub Desktop.
Save gtokman/dcede29516df6a41d57bfdb729bb0cb8 to your computer and use it in GitHub Desktop.
Nofication
//
// NotificationService.swift
// notifcation
//
// Created by Gary Tokman on 6/27/25.
//
import UserNotifications
import Candle
import Security
import CoreLocation // 1. Import
@MainActor
final class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
let candleClient = CandleClient(appUser: .init(
appKey: "YOUR_KEY",
appSecret: "YOUR_KEY"),
accessGroup: "group.testnes"
)
let locationManager = CLLocationManager() // 2. Init Corelocation
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
Task {
do {
// 3. Get location
locationManager.requestWhenInUseAuthorization()
guard let currentLocation = locationManager.location else {
contentHandler(bestAttemptContent) // 4. Fallback
return
}
// Show just the password for the specific service key
let quotes = try await candleClient.getTradeQuotes(
request: .init(
gained: .TransportAssetQuoteRequest(
.init(assetKind: .transport,
originCoordinates: .init(
latitude: currentLocation.coordinate.latitude,
longitude: currentLocation.coordinate.longitude
),
destinationCoordinates: .init(
latitude: 40.750298,
longitude: -73.993324
)
)
),
lost: .FiatAssetQuoteRequest(.init(assetKind: .fiat))
)
)
if let account = quotes.linkedAccounts.first {
bestAttemptContent.title = "New \(account.service.name) Ride"
}
bestAttemptContent.subtitle = "\(quotes.tradeQuotes.count) available rides"
for quote in quotes.tradeQuotes {
// Ride info
switch quote.gained {
case .TransportAsset(let gainedResult):
if gainedResult.name == "Standard" {
// Fiat info
if case let .FiatAsset(lostResult) = quote.lost {
bestAttemptContent.body = "Price \(gainedResult.name) $\(lostResult.amount)"
if let attachment = await loadImage(url: gainedResult.imageURL) {
bestAttemptContent.attachments = [attachment]
}
}
}
default:
break
}
}
contentHandler(bestAttemptContent)
} catch {
print("Log to backend")
contentHandler(bestAttemptContent)
}
}
} else {
print("Log to backend")
}
}
func loadImage(url: String) async -> UNNotificationAttachment? {
if let url = URL(string: url),
let (data, _) = try? await URLSession.shared.data(from: url) {
let tempDir = FileManager.default.temporaryDirectory
let fileURL = tempDir.appendingPathComponent("ride.jpg")
try? data.write(to: fileURL)
if let attachment = try? UNNotificationAttachment(identifier: "ride", url: fileURL, options: nil) {
return attachment
}
}
return nil
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment