feat(ios): implement Phase 1 permission methods and fix build issues
Implement checkPermissionStatus() and requestNotificationPermissions() methods for iOS plugin, matching Android functionality. Fix compilation errors across plugin files and add comprehensive build/test infrastructure. Key Changes: - Add checkPermissionStatus() and requestNotificationPermissions() methods - Fix 13+ categories of Swift compilation errors (type conversions, logger API, access control, async/await, etc.) - Create DailyNotificationScheduler, DailyNotificationStorage, DailyNotificationStateActor, and DailyNotificationErrorCodes components - Fix CoreData initialization to handle missing model gracefully for Phase 1 - Add iOS test app build script with simulator auto-detection - Update directive with lessons learned from build and permission work Build Status: ✅ BUILD SUCCEEDED Test App: ✅ Ready for iOS Simulator testing Files Modified: - doc/directives/0003-iOS-Android-Parity-Directive.md (lessons learned) - ios/Plugin/DailyNotificationPlugin.swift (Phase 1 methods) - ios/Plugin/DailyNotificationModel.swift (CoreData fix) - 11+ other plugin files (compilation fixes) Files Added: - ios/Plugin/DailyNotificationScheduler.swift - ios/Plugin/DailyNotificationStorage.swift - ios/Plugin/DailyNotificationStateActor.swift - ios/Plugin/DailyNotificationErrorCodes.swift - scripts/build-ios-test-app.sh - scripts/setup-ios-test-app.sh - test-apps/ios-test-app/ (full test app) - Multiple Phase 1 documentation files
This commit is contained in:
@@ -15,19 +15,68 @@ import Foundation
|
||||
* This class encapsulates all the information needed for a notification
|
||||
* including scheduling, content, and metadata.
|
||||
*/
|
||||
class NotificationContent {
|
||||
class NotificationContent: Codable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
let id: String
|
||||
let title: String?
|
||||
let body: String?
|
||||
let scheduledTime: TimeInterval // milliseconds since epoch
|
||||
let fetchedAt: TimeInterval // milliseconds since epoch
|
||||
let scheduledTime: Int64 // milliseconds since epoch (matches Android long)
|
||||
let fetchedAt: Int64 // milliseconds since epoch (matches Android long)
|
||||
let url: String?
|
||||
let payload: [String: Any]?
|
||||
let etag: String?
|
||||
|
||||
// MARK: - Codable Support
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case title
|
||||
case body
|
||||
case scheduledTime
|
||||
case fetchedAt
|
||||
case url
|
||||
case payload
|
||||
case etag
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.decode(String.self, forKey: .id)
|
||||
title = try container.decodeIfPresent(String.self, forKey: .title)
|
||||
body = try container.decodeIfPresent(String.self, forKey: .body)
|
||||
scheduledTime = try container.decode(Int64.self, forKey: .scheduledTime)
|
||||
fetchedAt = try container.decode(Int64.self, forKey: .fetchedAt)
|
||||
url = try container.decodeIfPresent(String.self, forKey: .url)
|
||||
// payload is encoded as JSON string
|
||||
if let payloadString = try? container.decodeIfPresent(String.self, forKey: .payload),
|
||||
let payloadData = payloadString.data(using: .utf8),
|
||||
let payloadDict = try? JSONSerialization.jsonObject(with: payloadData) as? [String: Any] {
|
||||
payload = payloadDict
|
||||
} else {
|
||||
payload = nil
|
||||
}
|
||||
etag = try container.decodeIfPresent(String.self, forKey: .etag)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(id, forKey: .id)
|
||||
try container.encodeIfPresent(title, forKey: .title)
|
||||
try container.encodeIfPresent(body, forKey: .body)
|
||||
try container.encode(scheduledTime, forKey: .scheduledTime)
|
||||
try container.encode(fetchedAt, forKey: .fetchedAt)
|
||||
try container.encodeIfPresent(url, forKey: .url)
|
||||
// Encode payload as JSON string
|
||||
if let payload = payload,
|
||||
let payloadData = try? JSONSerialization.data(withJSONObject: payload),
|
||||
let payloadString = String(data: payloadData, encoding: .utf8) {
|
||||
try container.encode(payloadString, forKey: .payload)
|
||||
}
|
||||
try container.encodeIfPresent(etag, forKey: .etag)
|
||||
}
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/**
|
||||
@@ -45,8 +94,8 @@ class NotificationContent {
|
||||
init(id: String,
|
||||
title: String?,
|
||||
body: String?,
|
||||
scheduledTime: TimeInterval,
|
||||
fetchedAt: TimeInterval,
|
||||
scheduledTime: Int64,
|
||||
fetchedAt: Int64,
|
||||
url: String?,
|
||||
payload: [String: Any]?,
|
||||
etag: String?) {
|
||||
@@ -69,7 +118,7 @@ class NotificationContent {
|
||||
* @return Scheduled time as Date object
|
||||
*/
|
||||
func getScheduledTimeAsDate() -> Date {
|
||||
return Date(timeIntervalSince1970: scheduledTime / 1000)
|
||||
return Date(timeIntervalSince1970: Double(scheduledTime) / 1000.0)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +127,7 @@ class NotificationContent {
|
||||
* @return Fetched time as Date object
|
||||
*/
|
||||
func getFetchedTimeAsDate() -> Date {
|
||||
return Date(timeIntervalSince1970: fetchedAt / 1000)
|
||||
return Date(timeIntervalSince1970: Double(fetchedAt) / 1000.0)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,7 +162,8 @@ class NotificationContent {
|
||||
* @return true if scheduled time is in the future
|
||||
*/
|
||||
func isInTheFuture() -> Bool {
|
||||
return scheduledTime > Date().timeIntervalSince1970 * 1000
|
||||
let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
|
||||
return scheduledTime > currentTime
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,7 +172,7 @@ class NotificationContent {
|
||||
* @return Age in seconds at scheduled time
|
||||
*/
|
||||
func getAgeAtScheduledTime() -> TimeInterval {
|
||||
return (scheduledTime - fetchedAt) / 1000
|
||||
return Double(scheduledTime - fetchedAt) / 1000.0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,9 +200,26 @@ class NotificationContent {
|
||||
* @return NotificationContent instance
|
||||
*/
|
||||
static func fromDictionary(_ dict: [String: Any]) -> NotificationContent? {
|
||||
guard let id = dict["id"] as? String,
|
||||
let scheduledTime = dict["scheduledTime"] as? TimeInterval,
|
||||
let fetchedAt = dict["fetchedAt"] as? TimeInterval else {
|
||||
guard let id = dict["id"] as? String else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle both Int64 and TimeInterval (Double) for backward compatibility
|
||||
let scheduledTime: Int64
|
||||
if let time = dict["scheduledTime"] as? Int64 {
|
||||
scheduledTime = time
|
||||
} else if let time = dict["scheduledTime"] as? Double {
|
||||
scheduledTime = Int64(time)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let fetchedAt: Int64
|
||||
if let time = dict["fetchedAt"] as? Int64 {
|
||||
fetchedAt = time
|
||||
} else if let time = dict["fetchedAt"] as? Double {
|
||||
fetchedAt = Int64(time)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user