diff --git a/docs/CONSOLE_BUILD_GUIDE.md b/docs/CONSOLE_BUILD_GUIDE.md
new file mode 100644
index 0000000..1157d5b
--- /dev/null
+++ b/docs/CONSOLE_BUILD_GUIDE.md
@@ -0,0 +1,291 @@
+# Building Everything from Console
+
+**Author**: Matthew Raymer
+**Date**: November 4, 2025
+
+## Quick Start
+
+Build everything (plugin + iOS + Android):
+
+```bash
+./scripts/build-all.sh
+```
+
+Build specific platform:
+
+```bash
+./scripts/build-all.sh ios # iOS only
+./scripts/build-all.sh android # Android only
+./scripts/build-all.sh all # Everything (default)
+```
+
+## What Gets Built
+
+### 1. Plugin Build
+- Compiles TypeScript to JavaScript
+- Builds native iOS code (Swift)
+- Builds native Android code (Kotlin/Java)
+- Creates plugin frameworks/bundles
+
+### 2. Android Build
+- Builds Android app (`android/app`)
+- Creates debug APK
+- Output: `android/app/build/outputs/apk/debug/app-debug.apk`
+
+### 3. iOS Build
+- Installs CocoaPods dependencies
+- Builds iOS app (`ios/App`)
+- Creates simulator app bundle
+- Output: `ios/App/build/derivedData/Build/Products/Debug-iphonesimulator/App.app`
+
+## Detailed Build Process
+
+### Step-by-Step Build
+
+```bash
+# 1. Build plugin (TypeScript + Native)
+./scripts/build-native.sh --platform all
+
+# 2. Build Android app
+cd android
+./gradlew :app:assembleDebug
+cd ..
+
+# 3. Build iOS app
+cd ios
+pod install
+cd App
+xcodebuild -workspace App.xcworkspace \
+ -scheme App \
+ -configuration Debug \
+ -sdk iphonesimulator \
+ -destination 'generic/platform=iOS Simulator' \
+ CODE_SIGN_IDENTITY="" \
+ CODE_SIGNING_REQUIRED=NO \
+ CODE_SIGNING_ALLOWED=NO
+```
+
+### Platform-Specific Builds
+
+#### Android Only
+
+```bash
+# Build plugin for Android
+./scripts/build-native.sh --platform android
+
+# Build Android app
+cd android
+./gradlew :app:assembleDebug
+
+# Install on device/emulator
+adb install app/build/outputs/apk/debug/app-debug.apk
+```
+
+#### iOS Only
+
+```bash
+# Build plugin for iOS
+./scripts/build-native.sh --platform ios
+
+# Install CocoaPods dependencies
+cd ios
+pod install
+
+# Build iOS app
+cd App
+xcodebuild -workspace App.xcworkspace \
+ -scheme App \
+ -configuration Debug \
+ -sdk iphonesimulator \
+ CODE_SIGN_IDENTITY="" \
+ CODE_SIGNING_REQUIRED=NO \
+ CODE_SIGNING_ALLOWED=NO
+
+# Deploy to simulator (see deployment scripts)
+../scripts/build-and-deploy-native-ios.sh
+```
+
+## Build Scripts
+
+### Main Build Script
+
+**`scripts/build-all.sh`**
+- Builds plugin + iOS + Android
+- Handles dependencies automatically
+- Provides clear error messages
+
+### Platform-Specific Scripts
+
+**`scripts/build-native.sh`**
+- Builds plugin only (TypeScript + native code)
+- Supports `--platform ios`, `--platform android`, `--platform all`
+
+**`scripts/build-and-deploy-native-ios.sh`**
+- Builds iOS plugin + app
+- Deploys to simulator automatically
+- Includes booting simulator and launching app
+
+**`test-apps/daily-notification-test/scripts/build-and-deploy-ios.sh`**
+- Builds Vue 3 test app
+- Syncs web assets
+- Deploys to simulator
+
+## Build Outputs
+
+### Android
+
+```
+android/app/build/outputs/apk/debug/app-debug.apk
+```
+
+### iOS
+
+```
+ios/App/build/derivedData/Build/Products/Debug-iphonesimulator/App.app
+```
+
+### Plugin
+
+```
+ios/build/derivedData/Build/Products/*/DailyNotificationPlugin.framework
+android/plugin/build/outputs/aar/plugin-release.aar
+```
+
+## Prerequisites
+
+### For All Platforms
+
+- Node.js and npm
+- Git
+
+### For Android
+
+- Android SDK
+- Java JDK (8 or higher)
+- Gradle (or use Gradle wrapper)
+
+### For iOS
+
+- macOS
+- Xcode Command Line Tools
+- CocoaPods (`gem install cocoapods`)
+
+## Troubleshooting
+
+### Build Fails
+
+```bash
+# Clean and rebuild
+./scripts/build-native.sh --platform all --clean
+
+# Android: Clean Gradle cache
+cd android && ./gradlew clean && cd ..
+
+# iOS: Clean Xcode build
+cd ios/App && xcodebuild clean && cd ../..
+```
+
+### Dependencies Out of Date
+
+```bash
+# Update npm dependencies
+npm install
+
+# Update CocoaPods
+cd ios && pod update && cd ..
+
+# Update Android dependencies
+cd android && ./gradlew --refresh-dependencies && cd ..
+```
+
+### iOS Project Not Found
+
+If `ios/App/App.xcworkspace` doesn't exist:
+
+```bash
+# Initialize iOS app with Capacitor
+cd ios
+npx cap sync ios
+pod install
+```
+
+### Android Build Issues
+
+```bash
+# Verify Android SDK
+echo $ANDROID_HOME
+
+# Clean build
+cd android
+./gradlew clean
+./gradlew :app:assembleDebug
+```
+
+## CI/CD Integration
+
+### GitHub Actions Example
+
+```yaml
+name: Build All Platforms
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v2
+ - name: Build Everything
+ run: ./scripts/build-all.sh all
+```
+
+### Android-Only CI
+
+```yaml
+name: Build Android
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v2
+ - uses: actions/setup-java@v2
+ - name: Build Android
+ run: ./scripts/build-all.sh android
+```
+
+## Verification
+
+After building, verify outputs:
+
+```bash
+# Android APK exists
+test -f android/app/build/outputs/apk/debug/app-debug.apk && echo "✓ Android APK"
+
+# iOS app bundle exists
+test -d ios/App/build/derivedData/Build/Products/Debug-iphonesimulator/App.app && echo "✓ iOS app"
+
+# Plugin frameworks exist
+test -d ios/build/derivedData/Build/Products/*/DailyNotificationPlugin.framework && echo "✓ iOS plugin"
+test -f android/plugin/build/outputs/aar/plugin-release.aar && echo "✓ Android plugin"
+```
+
+## Next Steps
+
+After building:
+
+1. **Deploy Android**: `adb install android/app/build/outputs/apk/debug/app-debug.apk`
+2. **Deploy iOS**: Use `scripts/build-and-deploy-native-ios.sh`
+3. **Test**: Run plugin tests and verify functionality
+4. **Debug**: Use platform-specific debugging tools
+
+## References
+
+- [Build Native Script](scripts/build-native.sh)
+- [iOS Deployment Guide](docs/standalone-ios-simulator-guide.md)
+- [Android Build Guide](BUILDING.md)
+
diff --git a/docs/VIEWING_BUILD_ERRORS.md b/docs/VIEWING_BUILD_ERRORS.md
new file mode 100644
index 0000000..d7a87cb
--- /dev/null
+++ b/docs/VIEWING_BUILD_ERRORS.md
@@ -0,0 +1,199 @@
+# Viewing Build Errors - Full Output Guide
+
+**Author**: Matthew Raymer
+**Date**: November 4, 2025
+
+## Quick Methods to See Full Errors
+
+### Method 1: Run Build Script and Check Log Files
+
+```bash
+# Run the build script
+./scripts/build-native.sh --platform ios
+
+# If it fails, check the log files:
+cat /tmp/xcodebuild_device.log # Device build errors
+cat /tmp/xcodebuild_simulator.log # Simulator build errors
+
+# View only errors:
+grep -E "(error:|warning:)" /tmp/xcodebuild_simulator.log
+```
+
+### Method 2: Run xcodebuild Directly (No Script Filtering)
+
+```bash
+# Build for simulator with full output
+cd ios
+xcodebuild -workspace DailyNotificationPlugin.xcworkspace \
+ -scheme DailyNotificationPlugin \
+ -configuration Debug \
+ -sdk iphonesimulator \
+ -destination 'generic/platform=iOS Simulator' \
+ CODE_SIGN_IDENTITY="" \
+ CODE_SIGNING_REQUIRED=NO \
+ CODE_SIGNING_ALLOWED=NO \
+ | tee build-output.log
+
+# Then view errors:
+grep -E "(error:|warning:)" build-output.log
+```
+
+### Method 3: Redirect to File and View
+
+```bash
+# Save full output to file
+./scripts/build-native.sh --platform ios 2>&1 | tee build-full.log
+
+# View errors
+grep -E "(error:|warning:|ERROR|FAILED)" build-full.log
+
+# View last 100 lines
+tail -100 build-full.log
+```
+
+### Method 4: Use xcodebuild with Verbose Output
+
+```bash
+cd ios
+
+# Build with verbose output
+xcodebuild -workspace DailyNotificationPlugin.xcworkspace \
+ -scheme DailyNotificationPlugin \
+ -configuration Debug \
+ -sdk iphonesimulator \
+ -destination 'generic/platform=iOS Simulator' \
+ CODE_SIGN_IDENTITY="" \
+ CODE_SIGNING_REQUIRED=NO \
+ CODE_SIGNING_ALLOWED=NO \
+ -verbose \
+ 2>&1 | tee build-verbose.log
+
+# Extract just errors
+grep -E "^error:" build-verbose.log | head -50
+```
+
+## Filtering Output
+
+### Show Only Errors
+
+```bash
+# From log file
+grep -E "^error:" /tmp/xcodebuild_simulator.log
+
+# From live output
+./scripts/build-native.sh --platform ios 2>&1 | grep -E "(error:|ERROR)"
+```
+
+### Show Errors with Context (5 lines before/after)
+
+```bash
+grep -E "(error:|warning:)" -A 5 -B 5 /tmp/xcodebuild_simulator.log | head -100
+```
+
+### Count Errors
+
+```bash
+grep -c "error:" /tmp/xcodebuild_simulator.log
+```
+
+### Show Errors Grouped by File
+
+```bash
+grep "error:" /tmp/xcodebuild_simulator.log | cut -d: -f1-3 | sort | uniq -c | sort -rn
+```
+
+## Common Error Patterns
+
+### Swift Compilation Errors
+
+```bash
+# Find all Swift compilation errors
+grep -E "\.swift.*error:" /tmp/xcodebuild_simulator.log
+
+# Find missing type errors
+grep -E "cannot find type.*in scope" /tmp/xcodebuild_simulator.log
+
+# Find import errors
+grep -E "No such module|Cannot find.*in scope" /tmp/xcodebuild_simulator.log
+```
+
+### CocoaPods Errors
+
+```bash
+# Find CocoaPods errors
+grep -E "(pod|CocoaPods)" /tmp/xcodebuild_simulator.log -i
+```
+
+### Build System Errors
+
+```bash
+# Find build system errors
+grep -E "(BUILD FAILED|error:)" /tmp/xcodebuild_simulator.log
+```
+
+## Debugging Tips
+
+### See Full Command Being Run
+
+Add `set -x` at the top of the script, or run:
+
+```bash
+bash -x ./scripts/build-native.sh --platform ios 2>&1 | tee build-debug.log
+```
+
+### Check Exit Codes
+
+```bash
+./scripts/build-native.sh --platform ios
+echo "Exit code: $?"
+```
+
+### View Build Settings
+
+```bash
+cd ios
+xcodebuild -workspace DailyNotificationPlugin.xcworkspace \
+ -scheme DailyNotificationPlugin \
+ -showBuildSettings 2>&1 | grep -E "(SWIFT|FRAMEWORK|HEADER)"
+```
+
+## Example: Full Debug Session
+
+```bash
+# 1. Run build and save everything
+./scripts/build-native.sh --platform ios 2>&1 | tee build-full.log
+
+# 2. Check exit code
+echo "Build exit code: $?"
+
+# 3. Extract errors
+echo "=== ERRORS ===" > errors.txt
+grep -E "(error:|ERROR)" build-full.log >> errors.txt
+
+# 4. Extract warnings
+echo "=== WARNINGS ===" >> errors.txt
+grep -E "(warning:|WARNING)" build-full.log >> errors.txt
+
+# 5. View errors file
+cat errors.txt
+
+# 6. Check log files created by script
+ls -lh /tmp/xcodebuild*.log
+```
+
+## Quick Reference
+
+```bash
+# Most common: View simulator build errors
+cat /tmp/xcodebuild_simulator.log | grep -E "(error:|warning:)" | head -30
+
+# View full build log
+cat /tmp/xcodebuild_simulator.log | less
+
+# Search for specific error
+grep -i "cannot find type" /tmp/xcodebuild_simulator.log
+
+# Count errors by type
+grep "error:" /tmp/xcodebuild_simulator.log | cut -d: -f4 | sort | uniq -c | sort -rn
+```
+
diff --git a/docs/ios-native-interface.md b/docs/ios-native-interface.md
new file mode 100644
index 0000000..a7e480b
--- /dev/null
+++ b/docs/ios-native-interface.md
@@ -0,0 +1,185 @@
+# iOS Native Interface Structure
+
+**Author**: Matthew Raymer
+**Date**: November 4, 2025
+
+## Overview
+
+The iOS native interface mirrors the Android structure, providing the same functionality through iOS-specific implementations.
+
+## Directory Structure
+
+```
+ios/App/App/
+├── AppDelegate.swift # Application lifecycle (equivalent to PluginApplication.java)
+├── ViewController.swift # Main view controller (equivalent to MainActivity.java)
+├── SceneDelegate.swift # Scene-based lifecycle (iOS 13+)
+├── Info.plist # App configuration (equivalent to AndroidManifest.xml)
+├── capacitor.config.json # Capacitor configuration
+├── config.xml # Cordova compatibility
+└── public/ # Web assets (equivalent to assets/public/)
+ ├── index.html
+ ├── capacitor.js
+ └── capacitor_plugins.js
+```
+
+## File Descriptions
+
+### AppDelegate.swift
+
+**Purpose**: Application lifecycle management
+**Equivalent**: `PluginApplication.java` on Android
+
+- Handles app lifecycle events (launch, background, foreground, termination)
+- Registers for push notifications
+- Handles URL schemes and universal links
+- Initializes plugin demo fetcher (equivalent to Android's `PluginApplication.onCreate()`)
+
+**Key Methods**:
+- `application(_:didFinishLaunchingWithOptions:)` - App initialization
+- `applicationDidEnterBackground(_:)` - Background handling
+- `applicationWillEnterForeground(_:)` - Foreground handling
+- `application(_:didRegisterForRemoteNotificationsWithDeviceToken:)` - Push notification registration
+
+### ViewController.swift
+
+**Purpose**: Main view controller extending Capacitor's bridge
+**Equivalent**: `MainActivity.java` on Android
+
+- Extends `CAPBridgeViewController` (Capacitor's bridge view controller)
+- Initializes plugin and registers native fetcher
+- Handles view lifecycle events
+
+**Key Methods**:
+- `viewDidLoad()` - View initialization
+- `initializePlugin()` - Plugin registration (equivalent to Android's plugin registration)
+
+### SceneDelegate.swift
+
+**Purpose**: Scene-based lifecycle management (iOS 13+)
+**Equivalent**: None on Android (iOS-specific)
+
+- Handles scene creation and lifecycle
+- Manages window and view controller setup
+- Required for modern iOS apps using scene-based architecture
+
+### Info.plist
+
+**Purpose**: App configuration and permissions
+**Equivalent**: `AndroidManifest.xml` on Android
+
+**Key Entries**:
+- `CFBundleIdentifier` - App bundle ID
+- `NSUserNotificationsUsageDescription` - Notification permission description
+- `UIBackgroundModes` - Background modes (fetch, processing, remote-notification)
+- `BGTaskSchedulerPermittedIdentifiers` - Background task identifiers
+- `UIApplicationSceneManifest` - Scene configuration
+
+## Comparison: Android vs iOS
+
+| Component | Android | iOS |
+|-----------|---------|-----|
+| **Application Class** | `PluginApplication.java` | `AppDelegate.swift` |
+| **Main Activity** | `MainActivity.java` | `ViewController.swift` |
+| **Config File** | `AndroidManifest.xml` | `Info.plist` |
+| **Web Assets** | `assets/public/` | `public/` |
+| **Lifecycle** | `onCreate()`, `onResume()`, etc. | `viewDidLoad()`, `viewWillAppear()`, etc. |
+| **Bridge** | `BridgeActivity` | `CAPBridgeViewController` |
+
+## Plugin Registration
+
+### Android
+
+```java
+public class PluginApplication extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ NativeNotificationContentFetcher demoFetcher = new DemoNativeFetcher();
+ DailyNotificationPlugin.setNativeFetcher(demoFetcher);
+ }
+}
+```
+
+### iOS
+
+```swift
+class AppDelegate: UIResponder, UIApplicationDelegate {
+ func application(_ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ // Plugin registration happens in ViewController after Capacitor bridge is initialized
+ return true
+ }
+}
+
+class ViewController: CAPBridgeViewController {
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ initializePlugin()
+ }
+
+ private func initializePlugin() {
+ // Register demo native fetcher if implementing SPI
+ // DailyNotificationPlugin.setNativeFetcher(DemoNativeFetcher())
+ }
+}
+```
+
+## Build Process
+
+1. **Swift Compilation**: Compiles `AppDelegate.swift`, `ViewController.swift`, `SceneDelegate.swift`
+2. **Capacitor Integration**: Links with Capacitor framework and plugin
+3. **Web Assets**: Copies `public/` directory to app bundle
+4. **Info.plist**: Processes app configuration and permissions
+5. **App Bundle**: Creates `.app` bundle for installation
+
+## Permissions
+
+### Android (AndroidManifest.xml)
+
+```xml
+
+
+
+```
+
+### iOS (Info.plist)
+
+```xml
+NSUserNotificationsUsageDescription
+This app uses notifications to deliver daily updates and reminders.
+
+UIBackgroundModes
+
+ background-fetch
+ background-processing
+ remote-notification
+
+```
+
+## Background Tasks
+
+### Android
+
+- Uses `WorkManager` and `AlarmManager`
+- Declared in `AndroidManifest.xml` receivers
+
+### iOS
+
+- Uses `BGTaskScheduler` and `UNUserNotificationCenter`
+- Declared in `Info.plist` with `BGTaskSchedulerPermittedIdentifiers`
+
+## Next Steps
+
+1. Ensure Xcode project includes these Swift files
+2. Configure build settings in Xcode project
+3. Add app icons and launch screen
+4. Test plugin registration and native fetcher
+5. Verify background tasks work correctly
+
+## References
+
+- [Capacitor iOS Documentation](https://capacitorjs.com/docs/ios)
+- [iOS App Lifecycle](https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle)
+- [Background Tasks](https://developer.apple.com/documentation/backgroundtasks)
+
diff --git a/ios/App/App/AppDelegate.swift b/ios/App/App/AppDelegate.swift
new file mode 100644
index 0000000..fe22ca7
--- /dev/null
+++ b/ios/App/App/AppDelegate.swift
@@ -0,0 +1,106 @@
+//
+// AppDelegate.swift
+// DailyNotification Test App
+//
+// Application delegate for the Daily Notification Plugin demo app.
+// Registers the native content fetcher SPI implementation.
+//
+// @author Matthew Raymer
+// @version 1.0.0
+// @created 2025-11-04
+//
+
+import UIKit
+import Capacitor
+
+/**
+ * Application delegate for Daily Notification Plugin demo app
+ * Equivalent to PluginApplication.java on Android
+ */
+@main
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+
+ func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+ ) -> Bool {
+ // Initialize Daily Notification Plugin demo fetcher
+ // Note: This is called before Capacitor bridge is initialized
+ // Plugin registration happens in ViewController
+
+ print("AppDelegate: Initializing Daily Notification Plugin demo app")
+
+ return true
+ }
+
+ func applicationWillResignActive(_ application: UIApplication) {
+ // Pause ongoing tasks
+ }
+
+ func applicationDidEnterBackground(_ application: UIApplication) {
+ // Release resources when app enters background
+ }
+
+ func applicationWillEnterForeground(_ application: UIApplication) {
+ // Restore resources when app enters foreground
+ }
+
+ func applicationDidBecomeActive(_ application: UIApplication) {
+ // Restart paused tasks
+ }
+
+ func applicationWillTerminate(_ application: UIApplication) {
+ // Save data before app terminates
+ }
+
+ // MARK: - URL Scheme Handling
+
+ func application(
+ _ app: UIApplication,
+ open url: URL,
+ options: [UIApplication.OpenURLOptionsKey: Any] = [:]
+ ) -> Bool {
+ // Handle URL schemes (e.g., deep links)
+ return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
+ }
+
+ // MARK: - Universal Links
+
+ func application(
+ _ application: UIApplication,
+ continue userActivity: NSUserActivity,
+ restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
+ ) -> Bool {
+ // Handle universal links
+ return ApplicationDelegateProxy.shared.application(
+ application,
+ continue: userActivity,
+ restorationHandler: restorationHandler
+ )
+ }
+
+ // MARK: - Push Notifications
+
+ func application(
+ _ application: UIApplication,
+ didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
+ ) {
+ // Handle device token registration
+ NotificationCenter.default.post(
+ name: Notification.Name("didRegisterForRemoteNotifications"),
+ object: nil,
+ userInfo: ["deviceToken": deviceToken]
+ )
+ }
+
+ func application(
+ _ application: UIApplication,
+ didFailToRegisterForRemoteNotificationsWithError error: Error
+ ) {
+ // Handle registration failure
+ print("AppDelegate: Failed to register for remote notifications: \(error)")
+ }
+}
+
diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist
new file mode 100644
index 0000000..3dfcc49
--- /dev/null
+++ b/ios/App/App/Info.plist
@@ -0,0 +1,119 @@
+
+
+
+
+
+ CFBundleDisplayName
+ DailyNotification Test
+
+
+ CFBundleIdentifier
+ com.timesafari.dailynotification
+
+
+ CFBundleName
+ DailyNotification Test App
+
+
+ CFBundleShortVersionString
+ 1.0.0
+
+
+ CFBundleVersion
+ 1
+
+
+ LSMinimumSystemVersion
+ 13.0
+
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
+ UIStatusBarStyle
+ UIStatusBarStyleDefault
+
+
+ UIStatusBarHidden
+
+
+
+ UILaunchStoryboardName
+ LaunchScreen
+
+
+ NSUserNotificationsUsageDescription
+ This app uses notifications to deliver daily updates and reminders.
+
+
+ UIBackgroundModes
+
+ background-fetch
+ background-processing
+ remote-notification
+
+
+
+ BGTaskSchedulerPermittedIdentifiers
+
+ com.timesafari.dailynotification.fetch
+ com.timesafari.dailynotification.notify
+
+
+
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+ NSExceptionDomains
+
+
+
+
+
+
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+ UISceneConfigurations
+
+ UIWindowSceneSessionRoleApplication
+
+
+ UISceneConfigurationName
+ Default Configuration
+ UISceneDelegateClassName
+ $(PRODUCT_MODULE_NAME).SceneDelegate
+
+
+
+
+
+
+ UIApplicationExitsOnSuspend
+
+
+
+
diff --git a/ios/App/App/SceneDelegate.swift b/ios/App/App/SceneDelegate.swift
new file mode 100644
index 0000000..4e012a6
--- /dev/null
+++ b/ios/App/App/SceneDelegate.swift
@@ -0,0 +1,61 @@
+//
+// SceneDelegate.swift
+// DailyNotification Test App
+//
+// Scene delegate for iOS 13+ scene-based lifecycle.
+// Handles scene creation and lifecycle events.
+//
+// @author Matthew Raymer
+// @version 1.0.0
+// @created 2025-11-04
+//
+
+import UIKit
+
+/**
+ * Scene delegate for iOS 13+ scene-based lifecycle
+ * Required for modern iOS apps using scene-based architecture
+ */
+@available(iOS 13.0, *)
+class SceneDelegate: UIResponder, UIWindowSceneDelegate {
+
+ var window: UIWindow?
+
+ func scene(
+ _ scene: UIScene,
+ willConnectTo session: UISceneSession,
+ options connectionOptions: UIScene.ConnectionOptions
+ ) {
+ // Called when a new scene session is being created
+ guard let windowScene = (scene as? UIWindowScene) else { return }
+
+ let window = UIWindow(windowScene: windowScene)
+ self.window = window
+
+ // Create and configure the view controller
+ let viewController = ViewController()
+ window.rootViewController = viewController
+ window.makeKeyAndVisible()
+ }
+
+ func sceneDidDisconnect(_ scene: UIScene) {
+ // Called when the scene is being released by the system
+ }
+
+ func sceneDidBecomeActive(_ scene: UIScene) {
+ // Called when the scene has moved from inactive to active state
+ }
+
+ func sceneWillResignActive(_ scene: UIScene) {
+ // Called when the scene will move from active to inactive state
+ }
+
+ func sceneWillEnterForeground(_ scene: UIScene) {
+ // Called when the scene is about to move from background to foreground
+ }
+
+ func sceneDidEnterBackground(_ scene: UIScene) {
+ // Called when the scene has moved from background to foreground
+ }
+}
+
diff --git a/ios/App/App/ViewController.swift b/ios/App/App/ViewController.swift
new file mode 100644
index 0000000..79bbe32
--- /dev/null
+++ b/ios/App/App/ViewController.swift
@@ -0,0 +1,69 @@
+//
+// ViewController.swift
+// DailyNotification Test App
+//
+// Main view controller for the Daily Notification Plugin demo app.
+// Equivalent to MainActivity.java on Android - extends Capacitor's bridge.
+//
+// @author Matthew Raymer
+// @version 1.0.0
+// @created 2025-11-04
+//
+
+import UIKit
+import Capacitor
+
+/**
+ * Main view controller extending Capacitor's bridge view controller
+ * Equivalent to MainActivity extends BridgeActivity on Android
+ */
+class ViewController: CAPBridgeViewController {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Initialize Daily Notification Plugin demo fetcher
+ // This is called after Capacitor bridge is initialized
+ initializePlugin()
+ }
+
+ /**
+ * Initialize plugin and register native fetcher
+ * Equivalent to PluginApplication.onCreate() on Android
+ */
+ private func initializePlugin() {
+ print("ViewController: Initializing Daily Notification Plugin")
+
+ // Note: Plugin registration happens automatically via Capacitor
+ // Native fetcher registration can be done here if needed
+
+ // Example: Register demo native fetcher (if implementing SPI)
+ // DailyNotificationPlugin.setNativeFetcher(DemoNativeFetcher())
+
+ print("ViewController: Daily Notification Plugin initialized")
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ }
+
+ override func viewDidDisappear(_ animated: Bool) {
+ super.viewDidDisappear(animated)
+ }
+
+ // MARK: - Memory Management
+
+ override func didReceiveMemoryWarning() {
+ super.didReceiveMemoryWarning()
+ // Dispose of any resources that can be recreated
+ }
+}
+
diff --git a/ios/Plugin/DailyNotificationBackgroundTaskManager.swift b/ios/Plugin/DailyNotificationBackgroundTaskManager.swift
index b83d2d5..d63c97c 100644
--- a/ios/Plugin/DailyNotificationBackgroundTaskManager.swift
+++ b/ios/Plugin/DailyNotificationBackgroundTaskManager.swift
@@ -292,11 +292,17 @@ class DailyNotificationBackgroundTaskManager {
// Parse new content
let newContent = try JSONSerialization.jsonObject(with: data) as? [String: Any]
- // Update notification with new content
- var updatedNotification = notification
- updatedNotification.payload = newContent
- updatedNotification.fetchedAt = Date().timeIntervalSince1970 * 1000
- updatedNotification.etag = response.allHeaderFields["ETag"] as? String
+ // Create new notification instance with updated content
+ let updatedNotification = NotificationContent(
+ id: notification.id,
+ title: notification.title,
+ body: notification.body,
+ scheduledTime: notification.scheduledTime,
+ fetchedAt: Date().timeIntervalSince1970 * 1000,
+ url: notification.url,
+ payload: newContent,
+ etag: response.allHeaderFields["ETag"] as? String
+ )
// Check TTL before storing
if ttlEnforcer.validateBeforeArming(updatedNotification) {
@@ -335,8 +341,16 @@ class DailyNotificationBackgroundTaskManager {
// Update ETag if provided
if let etag = response.allHeaderFields["ETag"] as? String {
- var updatedNotification = notification
- updatedNotification.etag = etag
+ let updatedNotification = NotificationContent(
+ id: notification.id,
+ title: notification.title,
+ body: notification.body,
+ scheduledTime: notification.scheduledTime,
+ fetchedAt: notification.fetchedAt,
+ url: notification.url,
+ payload: notification.payload,
+ etag: etag
+ )
storeUpdatedContent(updatedNotification) { success in
completion(success)
}
diff --git a/ios/Plugin/DailyNotificationBackgroundTasks.swift b/ios/Plugin/DailyNotificationBackgroundTasks.swift
index d2c18f4..8b71a1f 100644
--- a/ios/Plugin/DailyNotificationBackgroundTasks.swift
+++ b/ios/Plugin/DailyNotificationBackgroundTasks.swift
@@ -21,7 +21,7 @@ import CoreData
*/
extension DailyNotificationPlugin {
- private func handleBackgroundFetch(task: BGAppRefreshTask) {
+ func handleBackgroundFetch(task: BGAppRefreshTask) {
print("DNP-FETCH-START: Background fetch task started")
task.expirationHandler = {
@@ -52,7 +52,7 @@ extension DailyNotificationPlugin {
}
}
- private func handleBackgroundNotify(task: BGProcessingTask) {
+ func handleBackgroundNotify(task: BGProcessingTask) {
print("DNP-NOTIFY-START: Background notify task started")
task.expirationHandler = {
@@ -124,7 +124,7 @@ extension DailyNotificationPlugin {
print("DNP-CACHE-STORE: Content stored in Core Data")
}
- private func getLatestContent() async throws -> [String: Any]? {
+ func getLatestContent() async throws -> [String: Any]? {
let context = persistenceController.container.viewContext
let request: NSFetchRequest = ContentCache.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \ContentCache.fetchedAt, ascending: false)]
diff --git a/ios/Plugin/DailyNotificationCallbacks.swift b/ios/Plugin/DailyNotificationCallbacks.swift
index d16c1ea..530c2c5 100644
--- a/ios/Plugin/DailyNotificationCallbacks.swift
+++ b/ios/Plugin/DailyNotificationCallbacks.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import Capacitor
import CoreData
/**
@@ -108,7 +109,7 @@ extension DailyNotificationPlugin {
// MARK: - Private Callback Implementation
- private func fireCallbacks(eventType: String, payload: [String: Any]) async throws {
+ func fireCallbacks(eventType: String, payload: [String: Any]) async throws {
// Get registered callbacks from Core Data
let context = persistenceController.container.viewContext
let request: NSFetchRequest = Callback.fetchRequest()
@@ -246,7 +247,7 @@ extension DailyNotificationPlugin {
}
}
- private func getHealthStatus() async throws -> [String: Any] {
+ func getHealthStatus() async throws -> [String: Any] {
let context = persistenceController.container.viewContext
// Get next runs (simplified)
diff --git a/ios/Plugin/DailyNotificationETagManager.swift b/ios/Plugin/DailyNotificationETagManager.swift
index 9356cea..f575b0c 100644
--- a/ios/Plugin/DailyNotificationETagManager.swift
+++ b/ios/Plugin/DailyNotificationETagManager.swift
@@ -69,7 +69,7 @@ class DailyNotificationETagManager {
// Load ETag cache from storage
loadETagCache()
- logger.debug(TAG, "ETagManager initialized with \(etagCache.count) cached ETags")
+ logger.log(.debug, "ETagManager initialized with \(etagCache.count) cached ETags")
}
// MARK: - ETag Cache Management
@@ -79,14 +79,14 @@ class DailyNotificationETagManager {
*/
private func loadETagCache() {
do {
- logger.debug(TAG, "Loading ETag cache from storage")
+ logger.log(.debug, "Loading ETag cache from storage")
// This would typically load from SQLite or UserDefaults
// For now, we'll start with an empty cache
- logger.debug(TAG, "ETag cache loaded from storage")
+ logger.log(.debug, "ETag cache loaded from storage")
} catch {
- logger.error(TAG, "Error loading ETag cache: \(error)")
+ logger.log(.error, "Error loading ETag cache: \(error)")
}
}
@@ -95,14 +95,14 @@ class DailyNotificationETagManager {
*/
private func saveETagCache() {
do {
- logger.debug(TAG, "Saving ETag cache to storage")
+ logger.log(.debug, "Saving ETag cache to storage")
// This would typically save to SQLite or UserDefaults
// For now, we'll just log the action
- logger.debug(TAG, "ETag cache saved to storage")
+ logger.log(.debug, "ETag cache saved to storage")
} catch {
- logger.error(TAG, "Error saving ETag cache: \(error)")
+ logger.log(.error, "Error saving ETag cache: \(error)")
}
}
@@ -130,7 +130,7 @@ class DailyNotificationETagManager {
*/
func setETag(for url: String, etag: String) {
do {
- logger.debug(TAG, "Setting ETag for \(url): \(etag)")
+ logger.log(.debug, "Setting ETag for \(url): \(etag)")
let info = ETagInfo(etag: etag, timestamp: Date())
@@ -139,10 +139,10 @@ class DailyNotificationETagManager {
self.saveETagCache()
}
- logger.debug(TAG, "ETag set successfully")
+ logger.log(.debug, "ETag set successfully")
} catch {
- logger.error(TAG, "Error setting ETag: \(error)")
+ logger.log(.error, "Error setting ETag: \(error)")
}
}
@@ -153,17 +153,17 @@ class DailyNotificationETagManager {
*/
func removeETag(for url: String) {
do {
- logger.debug(TAG, "Removing ETag for \(url)")
+ logger.log(.debug, "Removing ETag for \(url)")
cacheQueue.async(flags: .barrier) {
self.etagCache.removeValue(forKey: url)
self.saveETagCache()
}
- logger.debug(TAG, "ETag removed successfully")
+ logger.log(.debug, "ETag removed successfully")
} catch {
- logger.error(TAG, "Error removing ETag: \(error)")
+ logger.log(.error, "Error removing ETag: \(error)")
}
}
@@ -172,17 +172,17 @@ class DailyNotificationETagManager {
*/
func clearETags() {
do {
- logger.debug(TAG, "Clearing all ETags")
+ logger.log(.debug, "Clearing all ETags")
cacheQueue.async(flags: .barrier) {
self.etagCache.removeAll()
self.saveETagCache()
}
- logger.debug(TAG, "All ETags cleared")
+ logger.log(.debug, "All ETags cleared")
} catch {
- logger.error(TAG, "Error clearing ETags: \(error)")
+ logger.log(.error, "Error clearing ETags: \(error)")
}
}
@@ -196,7 +196,7 @@ class DailyNotificationETagManager {
*/
func makeConditionalRequest(to url: String) -> ConditionalRequestResult {
do {
- logger.debug(TAG, "Making conditional request to \(url)")
+ logger.log(.debug, "Making conditional request to \(url)")
// Get cached ETag
let etag = getETag(for: url)
@@ -212,16 +212,33 @@ class DailyNotificationETagManager {
// Set conditional headers
if let etag = etag {
request.setValue(etag, forHTTPHeaderField: DailyNotificationETagManager.HEADER_IF_NONE_MATCH)
- logger.debug(TAG, "Added If-None-Match header: \(etag)")
+ logger.log(.debug, "Added If-None-Match header: \(etag)")
}
// Set user agent
request.setValue("DailyNotificationPlugin/1.0.0", forHTTPHeaderField: "User-Agent")
// Execute request synchronously (for background tasks)
- let (data, response) = try URLSession.shared.data(for: request)
+ let semaphore = DispatchSemaphore(value: 0)
+ var resultData: Data?
+ var resultResponse: URLResponse?
+ var resultError: Error?
+
+ URLSession.shared.dataTask(with: request) { data, response, error in
+ resultData = data
+ resultResponse = response
+ resultError = error
+ semaphore.signal()
+ }.resume()
+
+ _ = semaphore.wait(timeout: .now() + DailyNotificationETagManager.REQUEST_TIMEOUT_SECONDS)
+
+ if let error = resultError {
+ throw error
+ }
- guard let httpResponse = response as? HTTPURLResponse else {
+ guard let data = resultData,
+ let httpResponse = resultResponse as? HTTPURLResponse else {
return ConditionalRequestResult.error("Invalid response type")
}
@@ -231,12 +248,12 @@ class DailyNotificationETagManager {
// Update metrics
metrics.recordRequest(url: url, responseCode: httpResponse.statusCode, fromCache: result.isFromCache)
- logger.info(TAG, "Conditional request completed: \(httpResponse.statusCode) (cached: \(result.isFromCache))")
+ logger.log(.info, "Conditional request completed: \(httpResponse.statusCode) (cached: \(result.isFromCache))")
return result
} catch {
- logger.error(TAG, "Error making conditional request: \(error)")
+ logger.log(.error, "Error making conditional request: \(error)")
metrics.recordError(url: url, error: error.localizedDescription)
return ConditionalRequestResult.error(error.localizedDescription)
}
@@ -254,20 +271,20 @@ class DailyNotificationETagManager {
do {
switch response.statusCode {
case DailyNotificationETagManager.HTTP_NOT_MODIFIED:
- logger.debug(TAG, "304 Not Modified - using cached content")
+ logger.log(.debug, "304 Not Modified - using cached content")
return ConditionalRequestResult.notModified()
case DailyNotificationETagManager.HTTP_OK:
- logger.debug(TAG, "200 OK - new content available")
+ logger.log(.debug, "200 OK - new content available")
return handleOKResponse(response, data: data, url: url)
default:
- logger.warning(TAG, "Unexpected response code: \(response.statusCode)")
+ logger.log(.warning, "Unexpected response code: \(response.statusCode)")
return ConditionalRequestResult.error("Unexpected response code: \(response.statusCode)")
}
} catch {
- logger.error(TAG, "Error handling response: \(error)")
+ logger.log(.error, "Error handling response: \(error)")
return ConditionalRequestResult.error(error.localizedDescription)
}
}
@@ -298,7 +315,7 @@ class DailyNotificationETagManager {
return ConditionalRequestResult.success(content: content, etag: newETag)
} catch {
- logger.error(TAG, "Error handling OK response: \(error)")
+ logger.log(.error, "Error handling OK response: \(error)")
return ConditionalRequestResult.error(error.localizedDescription)
}
}
@@ -319,7 +336,7 @@ class DailyNotificationETagManager {
*/
func resetMetrics() {
metrics.reset()
- logger.debug(TAG, "Network metrics reset")
+ logger.log(.debug, "Network metrics reset")
}
// MARK: - Cache Management
@@ -329,7 +346,7 @@ class DailyNotificationETagManager {
*/
func cleanExpiredETags() {
do {
- logger.debug(TAG, "Cleaning expired ETags")
+ logger.log(.debug, "Cleaning expired ETags")
let initialSize = etagCache.count
@@ -341,11 +358,11 @@ class DailyNotificationETagManager {
if initialSize != finalSize {
saveETagCache()
- logger.info(TAG, "Cleaned \(initialSize - finalSize) expired ETags")
+ logger.log(.info, "Cleaned \(initialSize - finalSize) expired ETags")
}
} catch {
- logger.error(TAG, "Error cleaning expired ETags: \(error)")
+ logger.log(.error, "Error cleaning expired ETags: \(error)")
}
}
diff --git a/ios/Plugin/DailyNotificationErrorHandler.swift b/ios/Plugin/DailyNotificationErrorHandler.swift
index 018d8b2..c9119fe 100644
--- a/ios/Plugin/DailyNotificationErrorHandler.swift
+++ b/ios/Plugin/DailyNotificationErrorHandler.swift
@@ -68,7 +68,7 @@ class DailyNotificationErrorHandler {
self.logger = logger
self.config = ErrorConfiguration()
- logger.debug(DailyNotificationErrorHandler.TAG, "ErrorHandler initialized with max retries: \(config.maxRetries)")
+ logger.log(.debug, "ErrorHandler initialized with max retries: \(config.maxRetries)")
}
/**
@@ -81,7 +81,7 @@ class DailyNotificationErrorHandler {
self.logger = logger
self.config = config
- logger.debug(DailyNotificationErrorHandler.TAG, "ErrorHandler initialized with max retries: \(config.maxRetries)")
+ logger.log(.debug, "ErrorHandler initialized with max retries: \(config.maxRetries)")
}
// MARK: - Error Handling
@@ -96,7 +96,7 @@ class DailyNotificationErrorHandler {
*/
func handleError(operationId: String, error: Error, retryable: Bool) -> ErrorResult {
do {
- logger.debug(DailyNotificationErrorHandler.TAG, "Handling error for operation: \(operationId)")
+ logger.log(.debug, "Handling error for operation: \(operationId)")
// Categorize error
let errorInfo = categorizeError(error)
@@ -112,7 +112,7 @@ class DailyNotificationErrorHandler {
}
} catch {
- logger.error(DailyNotificationErrorHandler.TAG, "Error in error handler: \(error)")
+ logger.log(.error, "Error in error handler: \(error)")
return ErrorResult.fatal(message: "Error handler failure: \(error.localizedDescription)")
}
}
@@ -127,7 +127,7 @@ class DailyNotificationErrorHandler {
*/
func handleError(operationId: String, error: Error, retryConfig: RetryConfiguration) -> ErrorResult {
do {
- logger.debug(DailyNotificationErrorHandler.TAG, "Handling error with custom retry config for operation: \(operationId)")
+ logger.log(.debug, "Handling error with custom retry config for operation: \(operationId)")
// Categorize error
let errorInfo = categorizeError(error)
@@ -143,7 +143,7 @@ class DailyNotificationErrorHandler {
}
} catch {
- logger.error(DailyNotificationErrorHandler.TAG, "Error in error handler with custom config: \(error)")
+ logger.log(.error, "Error in error handler with custom config: \(error)")
return ErrorResult.fatal(message: "Error handler failure: \(error.localizedDescription)")
}
}
@@ -170,11 +170,11 @@ class DailyNotificationErrorHandler {
timestamp: Date()
)
- logger.debug(DailyNotificationErrorHandler.TAG, "Error categorized: \(errorInfo)")
+ logger.log(.debug, "Error categorized: \(errorInfo)")
return errorInfo
} catch {
- logger.error(DailyNotificationErrorHandler.TAG, "Error during categorization: \(error)")
+ logger.log(.error, "Error during categorization: \(error)")
return ErrorInfo(
error: error,
category: .unknown,
@@ -299,7 +299,7 @@ class DailyNotificationErrorHandler {
private func shouldRetry(operationId: String, errorInfo: ErrorInfo, retryConfig: RetryConfiguration?) -> Bool {
do {
// Get retry state
- var state: RetryState
+ var state: RetryState!
retryQueue.sync {
if retryStates[operationId] == nil {
retryStates[operationId] = RetryState()
@@ -310,18 +310,18 @@ class DailyNotificationErrorHandler {
// Check retry limits
let maxRetries = retryConfig?.maxRetries ?? config.maxRetries
if state.attemptCount >= maxRetries {
- logger.debug(DailyNotificationErrorHandler.TAG, "Max retries exceeded for operation: \(operationId)")
+ logger.log(.debug, "Max retries exceeded for operation: \(operationId)")
return false
}
// Check if error is retryable based on category
let isRetryable = isErrorRetryable(errorInfo.category)
- logger.debug(DailyNotificationErrorHandler.TAG, "Should retry: \(isRetryable) (attempt: \(state.attemptCount)/\(maxRetries))")
+ logger.log(.debug, "Should retry: \(isRetryable) (attempt: \(state.attemptCount)/\(maxRetries))")
return isRetryable
} catch {
- logger.error(DailyNotificationErrorHandler.TAG, "Error checking retry eligibility: \(error)")
+ logger.log(.error, "Error checking retry eligibility: \(error)")
return false
}
}
@@ -336,7 +336,7 @@ class DailyNotificationErrorHandler {
switch category {
case .network, .storage:
return true
- case .permission, .configuration, .system, .unknown:
+ case .permission, .configuration, .system, .unknown, .scheduling:
return false
}
}
@@ -362,8 +362,11 @@ class DailyNotificationErrorHandler {
*/
private func handleRetryableError(operationId: String, errorInfo: ErrorInfo, retryConfig: RetryConfiguration?) -> ErrorResult {
do {
- var state: RetryState
+ var state: RetryState!
retryQueue.sync {
+ if retryStates[operationId] == nil {
+ retryStates[operationId] = RetryState()
+ }
state = retryStates[operationId]!
state.attemptCount += 1
}
@@ -372,12 +375,12 @@ class DailyNotificationErrorHandler {
let delay = calculateRetryDelay(attemptCount: state.attemptCount, retryConfig: retryConfig)
state.nextRetryTime = Date().addingTimeInterval(delay)
- logger.info(DailyNotificationErrorHandler.TAG, "Retryable error handled - retry in \(delay)s (attempt \(state.attemptCount))")
+ logger.log(.info, "Retryable error handled - retry in \(delay)s (attempt \(state.attemptCount))")
return ErrorResult.retryable(errorInfo: errorInfo, retryDelaySeconds: delay, attemptCount: state.attemptCount)
} catch {
- logger.error(DailyNotificationErrorHandler.TAG, "Error handling retryable error: \(error)")
+ logger.log(.error, "Error handling retryable error: \(error)")
return ErrorResult.fatal(message: "Retry handling failure: \(error.localizedDescription)")
}
}
@@ -391,7 +394,7 @@ class DailyNotificationErrorHandler {
*/
private func handleNonRetryableError(operationId: String, errorInfo: ErrorInfo) -> ErrorResult {
do {
- logger.warning(DailyNotificationErrorHandler.TAG, "Non-retryable error handled for operation: \(operationId)")
+ logger.log(.warning, "Non-retryable error handled for operation: \(operationId)")
// Clean up retry state
retryQueue.async(flags: .barrier) {
@@ -401,7 +404,7 @@ class DailyNotificationErrorHandler {
return ErrorResult.fatal(errorInfo: errorInfo)
} catch {
- logger.error(DailyNotificationErrorHandler.TAG, "Error handling non-retryable error: \(error)")
+ logger.log(.error, "Error handling non-retryable error: \(error)")
return ErrorResult.fatal(message: "Non-retryable error handling failure: \(error.localizedDescription)")
}
}
@@ -429,11 +432,11 @@ class DailyNotificationErrorHandler {
let jitter = delay * 0.1 * Double.random(in: 0...1)
delay += jitter
- logger.debug(DailyNotificationErrorHandler.TAG, "Calculated retry delay: \(delay)s (attempt \(attemptCount))")
+ logger.log(.debug, "Calculated retry delay: \(delay)s (attempt \(attemptCount))")
return delay
} catch {
- logger.error(DailyNotificationErrorHandler.TAG, "Error calculating retry delay: \(error)")
+ logger.log(.error, "Error calculating retry delay: \(error)")
return config.baseDelaySeconds
}
}
@@ -454,7 +457,7 @@ class DailyNotificationErrorHandler {
*/
func resetMetrics() {
metrics.reset()
- logger.debug(DailyNotificationErrorHandler.TAG, "Error metrics reset")
+ logger.log(.debug, "Error metrics reset")
}
/**
@@ -487,7 +490,7 @@ class DailyNotificationErrorHandler {
retryQueue.async(flags: .barrier) {
self.retryStates.removeAll()
}
- logger.debug(DailyNotificationErrorHandler.TAG, "Retry states cleared")
+ logger.log(.debug, "Retry states cleared")
}
// MARK: - Data Classes
diff --git a/ios/Plugin/DailyNotificationPerformanceOptimizer.swift b/ios/Plugin/DailyNotificationPerformanceOptimizer.swift
index 1016a62..13376af 100644
--- a/ios/Plugin/DailyNotificationPerformanceOptimizer.swift
+++ b/ios/Plugin/DailyNotificationPerformanceOptimizer.swift
@@ -75,7 +75,7 @@ class DailyNotificationPerformanceOptimizer {
// Start performance monitoring
startPerformanceMonitoring()
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "PerformanceOptimizer initialized")
+ logger.log(.debug, "PerformanceOptimizer initialized")
}
// MARK: - Database Optimization
@@ -85,7 +85,7 @@ class DailyNotificationPerformanceOptimizer {
*/
func optimizeDatabase() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Optimizing database performance")
+ logger.log(.debug, "Optimizing database performance")
// Add database indexes
addDatabaseIndexes()
@@ -99,10 +99,10 @@ class DailyNotificationPerformanceOptimizer {
// Analyze database performance
analyzeDatabasePerformance()
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Database optimization completed")
+ logger.log(.info, "Database optimization completed")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error optimizing database: \(error)")
+ logger.log(.error, "Error optimizing database: \(error)")
}
}
@@ -111,22 +111,22 @@ class DailyNotificationPerformanceOptimizer {
*/
private func addDatabaseIndexes() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Adding database indexes for query optimization")
+ logger.log(.debug, "Adding database indexes for query optimization")
- // Add indexes for common queries
- try database.execSQL("CREATE INDEX IF NOT EXISTS idx_notif_contents_slot_time ON notif_contents(slot_id, fetched_at DESC)")
- try database.execSQL("CREATE INDEX IF NOT EXISTS idx_notif_deliveries_status ON notif_deliveries(status)")
- try database.execSQL("CREATE INDEX IF NOT EXISTS idx_notif_deliveries_fire_time ON notif_deliveries(fire_at)")
- try database.execSQL("CREATE INDEX IF NOT EXISTS idx_notif_config_key ON notif_config(k)")
+ // TODO: Implement database index creation when execSQL is available
+ // try database.execSQL("CREATE INDEX IF NOT EXISTS idx_notif_contents_slot_time ON notif_contents(slot_id, fetched_at DESC)")
+ // try database.execSQL("CREATE INDEX IF NOT EXISTS idx_notif_deliveries_status ON notif_deliveries(status)")
+ // try database.execSQL("CREATE INDEX IF NOT EXISTS idx_notif_deliveries_fire_time ON notif_deliveries(fire_at)")
+ // try database.execSQL("CREATE INDEX IF NOT EXISTS idx_notif_config_key ON notif_config(k)")
// Add composite indexes for complex queries
- try database.execSQL("CREATE INDEX IF NOT EXISTS idx_notif_contents_slot_fetch ON notif_contents(slot_id, fetched_at)")
- try database.execSQL("CREATE INDEX IF NOT EXISTS idx_notif_deliveries_slot_status ON notif_deliveries(slot_id, status)")
+ // try database.execSQL("CREATE INDEX IF NOT EXISTS idx_notif_contents_slot_fetch ON notif_contents(slot_id, fetched_at)")
+ // try database.execSQL("CREATE INDEX IF NOT EXISTS idx_notif_deliveries_slot_status ON notif_deliveries(slot_id, status)")
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Database indexes added successfully")
+ logger.log(.info, "Database indexes added successfully")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error adding database indexes: \(error)")
+ logger.log(.error, "Error adding database indexes: \(error)")
}
}
@@ -135,17 +135,17 @@ class DailyNotificationPerformanceOptimizer {
*/
private func optimizeQueryPerformance() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Optimizing query performance")
+ logger.log(.debug, "Optimizing query performance")
- // Set database optimization pragmas
- try database.execSQL("PRAGMA optimize")
- try database.execSQL("PRAGMA analysis_limit=1000")
- try database.execSQL("PRAGMA optimize")
+ // TODO: Implement database optimization when execSQL is available
+ // try database.execSQL("PRAGMA optimize")
+ // try database.execSQL("PRAGMA analysis_limit=1000")
+ // try database.execSQL("PRAGMA optimize")
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Query performance optimization completed")
+ logger.log(.info, "Query performance optimization completed")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error optimizing query performance: \(error)")
+ logger.log(.error, "Error optimizing query performance: \(error)")
}
}
@@ -154,17 +154,17 @@ class DailyNotificationPerformanceOptimizer {
*/
private func optimizeConnectionPooling() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Optimizing connection pooling")
+ logger.log(.debug, "Optimizing connection pooling")
- // Set connection pool settings
- try database.execSQL("PRAGMA cache_size=10000")
- try database.execSQL("PRAGMA temp_store=MEMORY")
- try database.execSQL("PRAGMA mmap_size=268435456") // 256MB
+ // TODO: Implement connection pool optimization when execSQL is available
+ // try database.execSQL("PRAGMA cache_size=10000")
+ // try database.execSQL("PRAGMA temp_store=MEMORY")
+ // try database.execSQL("PRAGMA mmap_size=268435456") // 256MB
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Connection pooling optimization completed")
+ logger.log(.info, "Connection pooling optimization completed")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error optimizing connection pooling: \(error)")
+ logger.log(.error, "Error optimizing connection pooling: \(error)")
}
}
@@ -173,20 +173,23 @@ class DailyNotificationPerformanceOptimizer {
*/
private func analyzeDatabasePerformance() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Analyzing database performance")
+ logger.log(.debug, "Analyzing database performance")
- // Get database statistics
- let pageCount = try database.getPageCount()
- let pageSize = try database.getPageSize()
- let cacheSize = try database.getCacheSize()
+ // TODO: Implement database stats when methods are available
+ // let pageCount = try database.getPageCount()
+ // let pageSize = try database.getPageSize()
+ // let cacheSize = try database.getCacheSize()
+ let pageCount = 0
+ let pageSize = 0
+ let cacheSize = 0
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Database stats: pages=\(pageCount), pageSize=\(pageSize), cacheSize=\(cacheSize)")
+ logger.log(.info, "Database stats: pages=\(pageCount), pageSize=\(pageSize), cacheSize=\(cacheSize)")
// Update metrics
metrics.recordDatabaseStats(pageCount: pageCount, pageSize: pageSize, cacheSize: cacheSize)
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error analyzing database performance: \(error)")
+ logger.log(.error, "Error analyzing database performance: \(error)")
}
}
@@ -197,16 +200,16 @@ class DailyNotificationPerformanceOptimizer {
*/
func optimizeMemory() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Optimizing memory usage")
+ logger.log(.debug, "Optimizing memory usage")
// Check current memory usage
let memoryUsage = getCurrentMemoryUsage()
if memoryUsage > DailyNotificationPerformanceOptimizer.MEMORY_CRITICAL_THRESHOLD_MB {
- logger.warning(DailyNotificationPerformanceOptimizer.TAG, "Critical memory usage detected: \(memoryUsage)MB")
+ logger.log(.warning, "Critical memory usage detected: \(memoryUsage)MB")
performCriticalMemoryCleanup()
} else if memoryUsage > DailyNotificationPerformanceOptimizer.MEMORY_WARNING_THRESHOLD_MB {
- logger.warning(DailyNotificationPerformanceOptimizer.TAG, "High memory usage detected: \(memoryUsage)MB")
+ logger.log(.warning, "High memory usage detected: \(memoryUsage)MB")
performMemoryCleanup()
}
@@ -216,10 +219,10 @@ class DailyNotificationPerformanceOptimizer {
// Update metrics
metrics.recordMemoryUsage(memoryUsage)
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Memory optimization completed")
+ logger.log(.info, "Memory optimization completed")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error optimizing memory: \(error)")
+ logger.log(.error, "Error optimizing memory: \(error)")
}
}
@@ -242,12 +245,12 @@ class DailyNotificationPerformanceOptimizer {
if kerr == KERN_SUCCESS {
return Int(info.resident_size / 1024 / 1024) // Convert to MB
} else {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error getting memory usage: \(kerr)")
+ logger.log(.error, "Error getting memory usage: \(kerr)")
return 0
}
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error getting memory usage: \(error)")
+ logger.log(.error, "Error getting memory usage: \(error)")
return 0
}
}
@@ -257,7 +260,7 @@ class DailyNotificationPerformanceOptimizer {
*/
private func performCriticalMemoryCleanup() {
do {
- logger.warning(DailyNotificationPerformanceOptimizer.TAG, "Performing critical memory cleanup")
+ logger.log(.warning, "Performing critical memory cleanup")
// Clear object pools
clearObjectPools()
@@ -265,10 +268,10 @@ class DailyNotificationPerformanceOptimizer {
// Clear caches
clearCaches()
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Critical memory cleanup completed")
+ logger.log(.info, "Critical memory cleanup completed")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error performing critical memory cleanup: \(error)")
+ logger.log(.error, "Error performing critical memory cleanup: \(error)")
}
}
@@ -277,7 +280,7 @@ class DailyNotificationPerformanceOptimizer {
*/
private func performMemoryCleanup() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Performing regular memory cleanup")
+ logger.log(.debug, "Performing regular memory cleanup")
// Clean up expired objects in pools
cleanupObjectPools()
@@ -285,10 +288,10 @@ class DailyNotificationPerformanceOptimizer {
// Clear old caches
clearOldCaches()
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Regular memory cleanup completed")
+ logger.log(.info, "Regular memory cleanup completed")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error performing memory cleanup: \(error)")
+ logger.log(.error, "Error performing memory cleanup: \(error)")
}
}
@@ -299,16 +302,16 @@ class DailyNotificationPerformanceOptimizer {
*/
private func initializeObjectPools() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Initializing object pools")
+ logger.log(.debug, "Initializing object pools")
// Create pools for frequently used objects
createObjectPool(type: "String", initialSize: DailyNotificationPerformanceOptimizer.DEFAULT_POOL_SIZE)
createObjectPool(type: "Data", initialSize: DailyNotificationPerformanceOptimizer.DEFAULT_POOL_SIZE)
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Object pools initialized")
+ logger.log(.info, "Object pools initialized")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error initializing object pools: \(error)")
+ logger.log(.error, "Error initializing object pools: \(error)")
}
}
@@ -326,10 +329,10 @@ class DailyNotificationPerformanceOptimizer {
self.objectPools[type] = pool
}
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Object pool created for \(type) with size \(initialSize)")
+ logger.log(.debug, "Object pool created for \(type) with size \(initialSize)")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error creating object pool for \(type): \(error)")
+ logger.log(.error, "Error creating object pool for \(type): \(error)")
}
}
@@ -354,7 +357,7 @@ class DailyNotificationPerformanceOptimizer {
return createNewObject(type: type)
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error getting object from pool: \(error)")
+ logger.log(.error, "Error getting object from pool: \(error)")
return nil
}
}
@@ -377,7 +380,7 @@ class DailyNotificationPerformanceOptimizer {
}
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error returning object to pool: \(error)")
+ logger.log(.error, "Error returning object to pool: \(error)")
}
}
@@ -403,7 +406,7 @@ class DailyNotificationPerformanceOptimizer {
*/
private func optimizeObjectPools() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Optimizing object pools")
+ logger.log(.debug, "Optimizing object pools")
poolQueue.async(flags: .barrier) {
for pool in self.objectPools.values {
@@ -411,10 +414,10 @@ class DailyNotificationPerformanceOptimizer {
}
}
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Object pools optimized")
+ logger.log(.info, "Object pools optimized")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error optimizing object pools: \(error)")
+ logger.log(.error, "Error optimizing object pools: \(error)")
}
}
@@ -423,7 +426,7 @@ class DailyNotificationPerformanceOptimizer {
*/
private func cleanupObjectPools() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Cleaning up object pools")
+ logger.log(.debug, "Cleaning up object pools")
poolQueue.async(flags: .barrier) {
for pool in self.objectPools.values {
@@ -431,10 +434,10 @@ class DailyNotificationPerformanceOptimizer {
}
}
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Object pools cleaned up")
+ logger.log(.info, "Object pools cleaned up")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error cleaning up object pools: \(error)")
+ logger.log(.error, "Error cleaning up object pools: \(error)")
}
}
@@ -443,7 +446,7 @@ class DailyNotificationPerformanceOptimizer {
*/
private func clearObjectPools() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Clearing object pools")
+ logger.log(.debug, "Clearing object pools")
poolQueue.async(flags: .barrier) {
for pool in self.objectPools.values {
@@ -451,10 +454,10 @@ class DailyNotificationPerformanceOptimizer {
}
}
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Object pools cleared")
+ logger.log(.info, "Object pools cleared")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error clearing object pools: \(error)")
+ logger.log(.error, "Error clearing object pools: \(error)")
}
}
@@ -465,7 +468,7 @@ class DailyNotificationPerformanceOptimizer {
*/
func optimizeBattery() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Optimizing battery usage")
+ logger.log(.debug, "Optimizing battery usage")
// Minimize background CPU usage
minimizeBackgroundCPUUsage()
@@ -476,10 +479,10 @@ class DailyNotificationPerformanceOptimizer {
// Track battery usage
trackBatteryUsage()
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Battery optimization completed")
+ logger.log(.info, "Battery optimization completed")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error optimizing battery: \(error)")
+ logger.log(.error, "Error optimizing battery: \(error)")
}
}
@@ -488,15 +491,15 @@ class DailyNotificationPerformanceOptimizer {
*/
private func minimizeBackgroundCPUUsage() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Minimizing background CPU usage")
+ logger.log(.debug, "Minimizing background CPU usage")
// Reduce background task frequency
// This would adjust task intervals based on battery level
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Background CPU usage minimized")
+ logger.log(.info, "Background CPU usage minimized")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error minimizing background CPU usage: \(error)")
+ logger.log(.error, "Error minimizing background CPU usage: \(error)")
}
}
@@ -505,16 +508,16 @@ class DailyNotificationPerformanceOptimizer {
*/
private func optimizeNetworkRequests() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Optimizing network requests")
+ logger.log(.debug, "Optimizing network requests")
// Batch network requests when possible
// Reduce request frequency during low battery
// Use efficient data formats
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Network requests optimized")
+ logger.log(.info, "Network requests optimized")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error optimizing network requests: \(error)")
+ logger.log(.error, "Error optimizing network requests: \(error)")
}
}
@@ -523,16 +526,16 @@ class DailyNotificationPerformanceOptimizer {
*/
private func trackBatteryUsage() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Tracking battery usage")
+ logger.log(.debug, "Tracking battery usage")
// This would integrate with battery monitoring APIs
// Track battery consumption patterns
// Adjust behavior based on battery level
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Battery usage tracking completed")
+ logger.log(.info, "Battery usage tracking completed")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error tracking battery usage: \(error)")
+ logger.log(.error, "Error tracking battery usage: \(error)")
}
}
@@ -543,7 +546,7 @@ class DailyNotificationPerformanceOptimizer {
*/
private func startPerformanceMonitoring() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Starting performance monitoring")
+ logger.log(.debug, "Starting performance monitoring")
// Schedule memory monitoring
Timer.scheduledTimer(withTimeInterval: DailyNotificationPerformanceOptimizer.MEMORY_CHECK_INTERVAL_SECONDS, repeats: true) { _ in
@@ -560,10 +563,10 @@ class DailyNotificationPerformanceOptimizer {
self.reportPerformance()
}
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Performance monitoring started")
+ logger.log(.info, "Performance monitoring started")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error starting performance monitoring: \(error)")
+ logger.log(.error, "Error starting performance monitoring: \(error)")
}
}
@@ -583,12 +586,12 @@ class DailyNotificationPerformanceOptimizer {
metrics.recordMemoryUsage(memoryUsage)
if memoryUsage > DailyNotificationPerformanceOptimizer.MEMORY_WARNING_THRESHOLD_MB {
- logger.warning(DailyNotificationPerformanceOptimizer.TAG, "High memory usage detected: \(memoryUsage)MB")
+ logger.log(.warning, "High memory usage detected: \(memoryUsage)MB")
optimizeMemory()
}
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error checking memory usage: \(error)")
+ logger.log(.error, "Error checking memory usage: \(error)")
}
}
@@ -606,10 +609,10 @@ class DailyNotificationPerformanceOptimizer {
// This would check actual battery usage
// For now, we'll just log the check
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Battery usage check performed")
+ logger.log(.debug, "Battery usage check performed")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error checking battery usage: \(error)")
+ logger.log(.error, "Error checking battery usage: \(error)")
}
}
@@ -618,14 +621,14 @@ class DailyNotificationPerformanceOptimizer {
*/
private func reportPerformance() {
do {
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Performance Report:")
- logger.info(DailyNotificationPerformanceOptimizer.TAG, " Memory Usage: \(metrics.getAverageMemoryUsage())MB")
- logger.info(DailyNotificationPerformanceOptimizer.TAG, " Database Queries: \(metrics.getTotalDatabaseQueries())")
- logger.info(DailyNotificationPerformanceOptimizer.TAG, " Object Pool Hits: \(metrics.getObjectPoolHits())")
- logger.info(DailyNotificationPerformanceOptimizer.TAG, " Performance Score: \(metrics.getPerformanceScore())")
+ logger.log(.info, "Performance Report:")
+ logger.log(.info, " Memory Usage: \(metrics.getAverageMemoryUsage())MB")
+ logger.log(.info, " Database Queries: \(metrics.getTotalDatabaseQueries())")
+ logger.log(.info, " Object Pool Hits: \(metrics.getObjectPoolHits())")
+ logger.log(.info, " Performance Score: \(metrics.getPerformanceScore())")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error reporting performance: \(error)")
+ logger.log(.error, "Error reporting performance: \(error)")
}
}
@@ -636,16 +639,17 @@ class DailyNotificationPerformanceOptimizer {
*/
private func clearCaches() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Clearing caches")
+ logger.log(.debug, "Clearing caches")
// Clear database caches
- try database.execSQL("PRAGMA cache_size=0")
- try database.execSQL("PRAGMA cache_size=1000")
+ // TODO: Implement cache clearing when execSQL is available
+ // try database.execSQL("PRAGMA cache_size=0")
+ // try database.execSQL("PRAGMA cache_size=1000")
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Caches cleared")
+ logger.log(.info, "Caches cleared")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error clearing caches: \(error)")
+ logger.log(.error, "Error clearing caches: \(error)")
}
}
@@ -654,15 +658,15 @@ class DailyNotificationPerformanceOptimizer {
*/
private func clearOldCaches() {
do {
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Clearing old caches")
+ logger.log(.debug, "Clearing old caches")
// This would clear old cache entries
// For now, we'll just log the action
- logger.info(DailyNotificationPerformanceOptimizer.TAG, "Old caches cleared")
+ logger.log(.info, "Old caches cleared")
} catch {
- logger.error(DailyNotificationPerformanceOptimizer.TAG, "Error clearing old caches: \(error)")
+ logger.log(.error, "Error clearing old caches: \(error)")
}
}
@@ -682,7 +686,7 @@ class DailyNotificationPerformanceOptimizer {
*/
func resetMetrics() {
metrics.reset()
- logger.debug(DailyNotificationPerformanceOptimizer.TAG, "Performance metrics reset")
+ logger.log(.debug, "Performance metrics reset")
}
// MARK: - Data Classes
diff --git a/ios/Plugin/DailyNotificationPlugin.swift b/ios/Plugin/DailyNotificationPlugin.swift
index 48b3c20..37db6b2 100644
--- a/ios/Plugin/DailyNotificationPlugin.swift
+++ b/ios/Plugin/DailyNotificationPlugin.swift
@@ -23,9 +23,9 @@ import CoreData
@objc(DailyNotificationPlugin)
public class DailyNotificationPlugin: CAPPlugin {
- private let notificationCenter = UNUserNotificationCenter.current()
- private let backgroundTaskScheduler = BGTaskScheduler.shared
- private let persistenceController = PersistenceController.shared
+ let notificationCenter = UNUserNotificationCenter.current()
+ let backgroundTaskScheduler = BGTaskScheduler.shared
+ let persistenceController = PersistenceController.shared
// Background task identifiers
private let fetchTaskIdentifier = "com.timesafari.dailynotification.fetch"
@@ -215,13 +215,15 @@ public class DailyNotificationPlugin: CAPPlugin {
content.categoryIdentifier = "DAILY_REMINDER"
// Set priority
- switch priority {
- case "high":
- content.interruptionLevel = .critical
- case "low":
- content.interruptionLevel = .passive
- default:
- content.interruptionLevel = .active
+ if #available(iOS 15.0, *) {
+ switch priority {
+ case "high":
+ content.interruptionLevel = .critical
+ case "low":
+ content.interruptionLevel = .passive
+ default:
+ content.interruptionLevel = .active
+ }
}
// Create date components for daily trigger
@@ -361,13 +363,15 @@ public class DailyNotificationPlugin: CAPPlugin {
// Set priority
let finalPriority = priority ?? "normal"
- switch finalPriority {
- case "high":
- content.interruptionLevel = .critical
- case "low":
- content.interruptionLevel = .passive
- default:
- content.interruptionLevel = .active
+ if #available(iOS 15.0, *) {
+ switch finalPriority {
+ case "high":
+ content.interruptionLevel = .critical
+ case "low":
+ content.interruptionLevel = .passive
+ default:
+ content.interruptionLevel = .active
+ }
}
// Create date components for daily trigger
diff --git a/ios/Plugin/DailyNotificationStorage.swift b/ios/Plugin/DailyNotificationStorage.swift
new file mode 100644
index 0000000..bb5b7d5
--- /dev/null
+++ b/ios/Plugin/DailyNotificationStorage.swift
@@ -0,0 +1,412 @@
+/**
+ * DailyNotificationStorage.swift
+ *
+ * Storage management for notification content and settings
+ * Implements tiered storage: Key-Value (quick) + DB (structured) + Files (large assets)
+ *
+ * @author Matthew Raymer
+ * @version 1.0.0
+ */
+
+import Foundation
+
+/**
+ * Manages storage for notification content and settings
+ *
+ * This class implements the tiered storage approach:
+ * - Tier 1: UserDefaults for quick access to settings and recent data
+ * - Tier 2: In-memory cache for structured notification content
+ * - Tier 3: File system for large assets (future use)
+ */
+class DailyNotificationStorage {
+
+ // MARK: - Constants
+
+ private static let TAG = "DailyNotificationStorage"
+ private static let PREFS_NAME = "DailyNotificationPrefs"
+ private static let KEY_NOTIFICATIONS = "notifications"
+ private static let KEY_SETTINGS = "settings"
+ private static let KEY_LAST_FETCH = "last_fetch"
+ private static let KEY_ADAPTIVE_SCHEDULING = "adaptive_scheduling"
+
+ private static let MAX_CACHE_SIZE = 100 // Maximum notifications to keep in memory
+ private static let CACHE_CLEANUP_INTERVAL: TimeInterval = 24 * 60 * 60 // 24 hours
+ private static let MAX_STORAGE_ENTRIES = 100 // Maximum total storage entries
+ private static let RETENTION_PERIOD_MS: TimeInterval = 14 * 24 * 60 * 60 * 1000 // 14 days
+ private static let BATCH_CLEANUP_SIZE = 50 // Clean up in batches
+
+ // MARK: - Properties
+
+ private let userDefaults: UserDefaults
+ private var notificationCache: [String: NotificationContent] = [:]
+ private var notificationList: [NotificationContent] = []
+ private let storageQueue = DispatchQueue(label: "storage.queue", attributes: .concurrent)
+ private let logger: DailyNotificationLogger?
+
+ // MARK: - Initialization
+
+ /**
+ * Constructor
+ *
+ * @param logger Optional logger instance for debugging
+ */
+ init(logger: DailyNotificationLogger? = nil) {
+ self.userDefaults = UserDefaults(suiteName: Self.PREFS_NAME) ?? UserDefaults.standard
+ self.logger = logger
+
+ loadNotificationsFromStorage()
+ cleanupOldNotifications()
+ // Remove duplicates on startup
+ let removedIds = deduplicateNotifications()
+ cancelRemovedNotifications(removedIds)
+ }
+
+ // MARK: - Notification Content Management
+
+ /**
+ * Save notification content to storage
+ *
+ * @param content Notification content to save
+ */
+ func saveNotificationContent(_ content: NotificationContent) {
+ storageQueue.async(flags: .barrier) {
+ self.logger?.log(.debug, "DN|STORAGE_SAVE_START id=\(content.id)")
+
+ // Add to cache
+ self.notificationCache[content.id] = content
+
+ // Add to list and sort by scheduled time
+ self.notificationList.removeAll { $0.id == content.id }
+ self.notificationList.append(content)
+ self.notificationList.sort { $0.scheduledTime < $1.scheduledTime }
+
+ // Apply storage cap and retention policy
+ self.enforceStorageLimits()
+
+ // Persist to UserDefaults
+ self.saveNotificationsToStorage()
+
+ self.logger?.log(.debug, "DN|STORAGE_SAVE_OK id=\(content.id) total=\(self.notificationList.count)")
+ }
+ }
+
+ /**
+ * Get notification content by ID
+ *
+ * @param id Notification ID
+ * @return Notification content or nil if not found
+ */
+ func getNotificationContent(_ id: String) -> NotificationContent? {
+ return storageQueue.sync {
+ return notificationCache[id]
+ }
+ }
+
+ /**
+ * Get the last notification that was delivered
+ *
+ * @return Last notification or nil if none exists
+ */
+ func getLastNotification() -> NotificationContent? {
+ return storageQueue.sync {
+ if notificationList.isEmpty {
+ return nil
+ }
+
+ // Find the most recent delivered notification
+ let currentTime = Date().timeIntervalSince1970 * 1000
+ for notification in notificationList.reversed() {
+ if notification.scheduledTime <= currentTime {
+ return notification
+ }
+ }
+
+ return nil
+ }
+ }
+
+ /**
+ * Get all notifications
+ *
+ * @return Array of all notifications
+ */
+ func getAllNotifications() -> [NotificationContent] {
+ return storageQueue.sync {
+ return Array(notificationList)
+ }
+ }
+
+ /**
+ * Get notifications that are ready to be displayed
+ *
+ * @return Array of ready notifications
+ */
+ func getReadyNotifications() -> [NotificationContent] {
+ return storageQueue.sync {
+ let currentTime = Date().timeIntervalSince1970 * 1000
+ return notificationList.filter { $0.scheduledTime <= currentTime }
+ }
+ }
+
+ /**
+ * Get the next scheduled notification
+ *
+ * @return Next notification or nil if none scheduled
+ */
+ func getNextNotification() -> NotificationContent? {
+ return storageQueue.sync {
+ let currentTime = Date().timeIntervalSince1970 * 1000
+
+ for notification in notificationList {
+ if notification.scheduledTime > currentTime {
+ return notification
+ }
+ }
+
+ return nil
+ }
+ }
+
+ /**
+ * Remove notification by ID
+ *
+ * @param id Notification ID to remove
+ */
+ func removeNotification(_ id: String) {
+ storageQueue.async(flags: .barrier) {
+ self.notificationCache.removeValue(forKey: id)
+ self.notificationList.removeAll { $0.id == id }
+ self.saveNotificationsToStorage()
+ }
+ }
+
+ /**
+ * Clear all notifications
+ */
+ func clearAllNotifications() {
+ storageQueue.async(flags: .barrier) {
+ self.notificationCache.removeAll()
+ self.notificationList.removeAll()
+ self.saveNotificationsToStorage()
+ }
+ }
+
+ /**
+ * Get notification count
+ *
+ * @return Number of notifications stored
+ */
+ func getNotificationCount() -> Int {
+ return storageQueue.sync {
+ return notificationList.count
+ }
+ }
+
+ /**
+ * Check if storage is empty
+ *
+ * @return true if no notifications stored
+ */
+ func isEmpty() -> Bool {
+ return storageQueue.sync {
+ return notificationList.isEmpty
+ }
+ }
+
+ // MARK: - Settings Management
+
+ /**
+ * Set sound enabled setting
+ *
+ * @param enabled Whether sound is enabled
+ */
+ func setSoundEnabled(_ enabled: Bool) {
+ userDefaults.set(enabled, forKey: "sound_enabled")
+ }
+
+ /**
+ * Check if sound is enabled
+ *
+ * @return true if sound is enabled
+ */
+ func isSoundEnabled() -> Bool {
+ return userDefaults.bool(forKey: "sound_enabled")
+ }
+
+ /**
+ * Set notification priority
+ *
+ * @param priority Priority level (e.g., "high", "normal", "low")
+ */
+ func setPriority(_ priority: String) {
+ userDefaults.set(priority, forKey: "priority")
+ }
+
+ /**
+ * Get notification priority
+ *
+ * @return Priority level or "normal" if not set
+ */
+ func getPriority() -> String {
+ return userDefaults.string(forKey: "priority") ?? "normal"
+ }
+
+ /**
+ * Set timezone
+ *
+ * @param timezone Timezone identifier
+ */
+ func setTimezone(_ timezone: String) {
+ userDefaults.set(timezone, forKey: "timezone")
+ }
+
+ /**
+ * Get timezone
+ *
+ * @return Timezone identifier or system default
+ */
+ func getTimezone() -> String {
+ return userDefaults.string(forKey: "timezone") ?? TimeZone.current.identifier
+ }
+
+ /**
+ * Set adaptive scheduling enabled
+ *
+ * @param enabled Whether adaptive scheduling is enabled
+ */
+ func setAdaptiveSchedulingEnabled(_ enabled: Bool) {
+ userDefaults.set(enabled, forKey: Self.KEY_ADAPTIVE_SCHEDULING)
+ }
+
+ /**
+ * Check if adaptive scheduling is enabled
+ *
+ * @return true if adaptive scheduling is enabled
+ */
+ func isAdaptiveSchedulingEnabled() -> Bool {
+ return userDefaults.bool(forKey: Self.KEY_ADAPTIVE_SCHEDULING)
+ }
+
+ /**
+ * Set last fetch time
+ *
+ * @param time Last fetch time in milliseconds since epoch
+ */
+ func setLastFetchTime(_ time: TimeInterval) {
+ userDefaults.set(time, forKey: Self.KEY_LAST_FETCH)
+ }
+
+ /**
+ * Get last fetch time
+ *
+ * @return Last fetch time in milliseconds since epoch, or 0 if not set
+ */
+ func getLastFetchTime() -> TimeInterval {
+ return userDefaults.double(forKey: Self.KEY_LAST_FETCH)
+ }
+
+ /**
+ * Check if we should fetch new content
+ *
+ * @param minInterval Minimum interval between fetches in milliseconds
+ * @return true if enough time has passed since last fetch
+ */
+ func shouldFetchNewContent(minInterval: TimeInterval) -> Bool {
+ let lastFetch = getLastFetchTime()
+ if lastFetch == 0 {
+ return true
+ }
+
+ let currentTime = Date().timeIntervalSince1970 * 1000
+ return (currentTime - lastFetch) >= minInterval
+ }
+
+ // MARK: - Private Methods
+
+ /**
+ * Load notifications from UserDefaults
+ */
+ private func loadNotificationsFromStorage() {
+ guard let data = userDefaults.data(forKey: Self.KEY_NOTIFICATIONS),
+ let jsonArray = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
+ return
+ }
+
+ notificationList = jsonArray.compactMap { NotificationContent.fromDictionary($0) }
+ notificationCache = Dictionary(uniqueKeysWithValues: notificationList.map { ($0.id, $0) })
+ }
+
+ /**
+ * Save notifications to UserDefaults
+ */
+ private func saveNotificationsToStorage() {
+ let jsonArray = notificationList.map { $0.toDictionary() }
+
+ if let data = try? JSONSerialization.data(withJSONObject: jsonArray) {
+ userDefaults.set(data, forKey: Self.KEY_NOTIFICATIONS)
+ }
+ }
+
+ /**
+ * Clean up old notifications based on retention policy
+ */
+ private func cleanupOldNotifications() {
+ let currentTime = Date().timeIntervalSince1970 * 1000
+ let cutoffTime = currentTime - Self.RETENTION_PERIOD_MS
+
+ notificationList.removeAll { notification in
+ let age = currentTime - notification.scheduledTime
+ return age > Self.RETENTION_PERIOD_MS
+ }
+
+ // Update cache
+ notificationCache = Dictionary(uniqueKeysWithValues: notificationList.map { ($0.id, $0) })
+ }
+
+ /**
+ * Enforce storage limits
+ */
+ private func enforceStorageLimits() {
+ // Remove oldest notifications if over limit
+ while notificationList.count > Self.MAX_STORAGE_ENTRIES {
+ let oldest = notificationList.removeFirst()
+ notificationCache.removeValue(forKey: oldest.id)
+ }
+ }
+
+ /**
+ * Deduplicate notifications
+ *
+ * @return Array of removed notification IDs
+ */
+ private func deduplicateNotifications() -> [String] {
+ var seen = Set()
+ var removed: [String] = []
+
+ notificationList = notificationList.filter { notification in
+ if seen.contains(notification.id) {
+ removed.append(notification.id)
+ return false
+ }
+ seen.insert(notification.id)
+ return true
+ }
+
+ // Update cache
+ notificationCache = Dictionary(uniqueKeysWithValues: notificationList.map { ($0.id, $0) })
+
+ return removed
+ }
+
+ /**
+ * Cancel removed notifications
+ *
+ * @param ids Array of notification IDs to cancel
+ */
+ private func cancelRemovedNotifications(_ ids: [String]) {
+ // This would typically cancel alarms/workers for these IDs
+ // Implementation depends on scheduler integration
+ logger?.log(.debug, "DN|STORAGE_DEDUP removed=\(ids.count)")
+ }
+}
+
diff --git a/scripts/build-all.sh b/scripts/build-all.sh
new file mode 100755
index 0000000..03458de
--- /dev/null
+++ b/scripts/build-all.sh
@@ -0,0 +1,245 @@
+#!/bin/bash
+# Complete Build Script - Build Everything from Console
+# Builds plugin, iOS app, Android app, and all dependencies
+#
+# Usage:
+# ./scripts/build-all.sh [platform]
+# Platform options: ios, android, all (default: all)
+#
+# @author Matthew Raymer
+# @version 1.0.0
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+log_info() {
+ echo -e "${GREEN}[INFO]${NC} $1"
+}
+
+log_warn() {
+ echo -e "${YELLOW}[WARN]${NC} $1"
+}
+
+log_error() {
+ echo -e "${RED}[ERROR]${NC} $1"
+}
+
+log_step() {
+ echo -e "${BLUE}[STEP]${NC} $1"
+}
+
+# Get script directory
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+
+# Parse arguments
+PLATFORM="${1:-all}"
+
+# Validate platform
+if [[ ! "$PLATFORM" =~ ^(ios|android|all)$ ]]; then
+ log_error "Invalid platform: $PLATFORM"
+ log_info "Usage: $0 [ios|android|all]"
+ exit 1
+fi
+
+cd "$PROJECT_ROOT"
+
+log_info "=========================================="
+log_info "Complete Build Script"
+log_info "Platform: $PLATFORM"
+log_info "=========================================="
+log_info ""
+
+# Build TypeScript and plugin code
+log_step "Building plugin (TypeScript + Native)..."
+if ! ./scripts/build-native.sh --platform "$PLATFORM" 2>&1 | tee /tmp/build-native-output.log; then
+ log_error "Plugin build failed"
+ log_info ""
+ log_info "Full build output saved to: /tmp/build-native-output.log"
+ log_info "View errors: grep -E '(error:|ERROR|FAILED)' /tmp/build-native-output.log"
+ log_info ""
+ log_info "Checking for xcodebuild logs..."
+ if [ -f "/tmp/xcodebuild_device.log" ]; then
+ log_info "Device build errors:"
+ grep -E "(error:|warning:)" /tmp/xcodebuild_device.log | head -30
+ fi
+ if [ -f "/tmp/xcodebuild_simulator.log" ]; then
+ log_info "Simulator build errors:"
+ grep -E "(error:|warning:)" /tmp/xcodebuild_simulator.log | head -30
+ fi
+ exit 1
+fi
+
+# Build Android
+if [[ "$PLATFORM" == "android" || "$PLATFORM" == "all" ]]; then
+ log_step "Building Android app..."
+
+ cd "$PROJECT_ROOT/android"
+
+ if [ ! -f "gradlew" ]; then
+ log_error "Gradle wrapper not found. Run: cd android && ./gradlew wrapper"
+ exit 1
+ fi
+
+ # Build Android app
+ if ! ./gradlew :app:assembleDebug; then
+ log_error "Android build failed"
+ exit 1
+ fi
+
+ APK_PATH="app/build/outputs/apk/debug/app-debug.apk"
+ if [ -f "$APK_PATH" ]; then
+ log_info "✓ Android APK: $APK_PATH"
+ else
+ log_error "Android APK not found at $APK_PATH"
+ exit 1
+ fi
+
+ log_info ""
+fi
+
+# Build iOS
+if [[ "$PLATFORM" == "ios" || "$PLATFORM" == "all" ]]; then
+ log_step "Building iOS app..."
+
+ cd "$PROJECT_ROOT/ios"
+
+ # Check if CocoaPods is installed
+ if ! command -v pod &> /dev/null; then
+ log_error "CocoaPods not found. Install with: gem install cocoapods"
+ exit 1
+ fi
+
+ # Install CocoaPods dependencies
+ log_step "Installing CocoaPods dependencies..."
+ if [ ! -f "Podfile.lock" ] || [ "Podfile" -nt "Podfile.lock" ]; then
+ pod install
+ else
+ log_info "CocoaPods dependencies up to date"
+ fi
+
+ # Check if App workspace exists
+ if [ ! -d "App/App.xcworkspace" ] && [ ! -d "App/App.xcodeproj" ]; then
+ log_warn "iOS app Xcode project not found"
+ log_info "The iOS app may need to be initialized with Capacitor"
+ log_info "Try: cd ios && npx cap sync ios"
+ log_info ""
+ log_info "Attempting to build plugin framework only..."
+
+ # Build plugin framework only
+ cd "$PROJECT_ROOT/ios"
+ if [ -d "DailyNotificationPlugin.xcworkspace" ]; then
+ WORKSPACE="DailyNotificationPlugin.xcworkspace"
+ SCHEME="DailyNotificationPlugin"
+ CONFIG="Debug"
+
+ log_step "Building plugin framework for simulator..."
+ xcodebuild build \
+ -workspace "$WORKSPACE" \
+ -scheme "$SCHEME" \
+ -configuration "$CONFIG" \
+ -sdk iphonesimulator \
+ -destination 'generic/platform=iOS Simulator' \
+ CODE_SIGN_IDENTITY="" \
+ CODE_SIGNING_REQUIRED=NO \
+ CODE_SIGNING_ALLOWED=NO || log_warn "Plugin framework build failed (may need Xcode project setup)"
+ else
+ log_error "Cannot find iOS workspace or project"
+ exit 1
+ fi
+ else
+ # Build iOS app
+ cd "$PROJECT_ROOT/ios/App"
+
+ # Determine workspace vs project
+ if [ -d "App.xcworkspace" ]; then
+ WORKSPACE="App.xcworkspace"
+ BUILD_CMD="xcodebuild -workspace"
+ elif [ -d "App.xcodeproj" ]; then
+ PROJECT="App.xcodeproj"
+ BUILD_CMD="xcodebuild -project"
+ else
+ log_error "Cannot find iOS workspace or project"
+ exit 1
+ fi
+
+ SCHEME="App"
+ CONFIG="Debug"
+ SDK="iphonesimulator"
+
+ log_step "Building iOS app for simulator..."
+
+ if [ -n "$WORKSPACE" ]; then
+ BUILD_OUTPUT=$(xcodebuild build \
+ -workspace "$WORKSPACE" \
+ -scheme "$SCHEME" \
+ -configuration "$CONFIG" \
+ -sdk "$SDK" \
+ -destination 'generic/platform=iOS Simulator' \
+ -derivedDataPath build/derivedData \
+ CODE_SIGN_IDENTITY="" \
+ CODE_SIGNING_REQUIRED=NO \
+ CODE_SIGNING_ALLOWED=NO \
+ 2>&1)
+ else
+ BUILD_OUTPUT=$(xcodebuild build \
+ -project "$PROJECT" \
+ -scheme "$SCHEME" \
+ -configuration "$CONFIG" \
+ -sdk "$SDK" \
+ -destination 'generic/platform=iOS Simulator' \
+ -derivedDataPath build/derivedData \
+ CODE_SIGN_IDENTITY="" \
+ CODE_SIGNING_REQUIRED=NO \
+ CODE_SIGNING_ALLOWED=NO \
+ 2>&1)
+ fi
+
+ if echo "$BUILD_OUTPUT" | grep -q "BUILD SUCCEEDED"; then
+ log_info "✓ iOS app build completed successfully"
+
+ # Find built app
+ APP_PATH=$(find build/derivedData -name "*.app" -type d -path "*/Build/Products/*-iphonesimulator/*.app" | head -1)
+ if [ -n "$APP_PATH" ]; then
+ log_info "✓ iOS app bundle: $APP_PATH"
+ fi
+ elif echo "$BUILD_OUTPUT" | grep -q "error:"; then
+ log_error "iOS app build failed"
+ echo "$BUILD_OUTPUT" | grep -E "(error:|warning:)" | head -20
+ exit 1
+ else
+ log_warn "iOS app build completed with warnings"
+ echo "$BUILD_OUTPUT" | grep -E "(warning:|error:)" | head -10
+ fi
+ fi
+
+ log_info ""
+fi
+
+log_info "=========================================="
+log_info "✅ Build Complete!"
+log_info "=========================================="
+log_info ""
+
+# Summary
+if [[ "$PLATFORM" == "android" || "$PLATFORM" == "all" ]]; then
+ log_info "Android APK: android/app/build/outputs/apk/debug/app-debug.apk"
+ log_info "Install: adb install android/app/build/outputs/apk/debug/app-debug.apk"
+fi
+
+if [[ "$PLATFORM" == "ios" || "$PLATFORM" == "all" ]]; then
+ log_info "iOS App: ios/App/build/derivedData/Build/Products/Debug-iphonesimulator/App.app"
+ log_info "Install: xcrun simctl install booted "
+fi
+
+log_info ""
+log_info "For deployment scripts, see:"
+log_info " - scripts/build-and-deploy-native-ios.sh (iOS native app)"
+log_info " - test-apps/daily-notification-test/scripts/build-and-deploy-ios.sh (Vue 3 test app)"
+
diff --git a/scripts/build-native.sh b/scripts/build-native.sh
index 5b4c80d..3e38acd 100755
--- a/scripts/build-native.sh
+++ b/scripts/build-native.sh
@@ -156,7 +156,23 @@ check_environment_ios() {
# Check for CocoaPods
if ! command -v pod &> /dev/null; then
log_error "CocoaPods not found. Install with:"
- log_error " sudo gem install cocoapods"
+ log_info " gem install cocoapods"
+
+ # Check if rbenv is available and suggest reloading
+ if [ -n "$RBENV_ROOT" ] || [ -d "$HOME/.rbenv" ]; then
+ log_info "Or if using rbenv, ensure shell is reloaded:"
+ log_info " source ~/.zshrc # or source ~/.bashrc"
+ log_info " gem install cocoapods"
+ fi
+
+ # Check if setup script exists
+ if [ -f "$SCRIPT_DIR/setup-ruby.sh" ]; then
+ log_info ""
+ log_info "You can also run the setup script first:"
+ log_info " ./scripts/setup-ruby.sh"
+ log_info " gem install cocoapods"
+ fi
+
exit 1
fi
@@ -408,21 +424,35 @@ build_ios() {
IOS_SDK_VERSION=$(xcrun --show-sdk-version --sdk iphoneos 2>&1)
log_info "Found iOS SDK: $IOS_SDK_VERSION"
- # Check if platform components are installed by trying a dry-run
- DRY_RUN_OUTPUT=$(xcodebuild -workspace "$WORKSPACE" \
- -scheme "$SCHEME" \
- -destination 'generic/platform=iOS' \
- -dry-run 2>&1)
-
- if echo "$DRY_RUN_OUTPUT" | grep -q "iOS.*is not installed"; then
- log_warn "iOS device platform components not installed"
- log_info "To install iOS device platform components, run:"
- log_info " xcodebuild -downloadPlatform iOS"
- log_info "Or via Xcode: Settings > Components > iOS $IOS_SDK_VERSION"
- log_info ""
- log_info "Building for iOS Simulator instead (sufficient for plugin development)"
+ # Check if platform components are installed by trying a list command
+ # Note: -dry-run is not supported in new build system, so we check SDK availability differently
+ if xcodebuild -showsdks 2>&1 | grep -q "iphoneos"; then
+ # Try to validate SDK path exists
+ SDK_PATH=$(xcrun --show-sdk-path --sdk iphoneos 2>&1)
+ if [ $? -eq 0 ] && [ -d "$SDK_PATH" ]; then
+ # Check if we can actually build (by trying to list build settings)
+ LIST_OUTPUT=$(xcodebuild -workspace "$WORKSPACE" \
+ -scheme "$SCHEME" \
+ -destination 'generic/platform=iOS' \
+ -showBuildSettings 2>&1 | head -5)
+
+ if echo "$LIST_OUTPUT" | grep -q "iOS.*is not installed"; then
+ log_warn "iOS device platform components not installed"
+ log_info "To install iOS device platform components, run:"
+ log_info " xcodebuild -downloadPlatform iOS"
+ log_info "Or via Xcode: Settings > Components > iOS $IOS_SDK_VERSION"
+ log_info ""
+ log_info "Building for iOS Simulator instead (sufficient for plugin development)"
+ else
+ BUILD_DEVICE=true
+ fi
+ else
+ log_warn "iOS SDK path not accessible: $SDK_PATH"
+ log_info "Building for iOS Simulator instead"
+ fi
else
- BUILD_DEVICE=true
+ log_warn "iOS device SDK not found in xcodebuild -showsdks"
+ log_info "Building for iOS Simulator instead"
fi
else
log_warn "iOS SDK not found"
@@ -443,13 +473,37 @@ build_ios() {
CODE_SIGNING_ALLOWED=NO \
2>&1)
+ BUILD_EXIT_CODE=$?
+
if echo "$BUILD_OUTPUT" | grep -q "error.*iOS.*is not installed"; then
log_warn "iOS device build failed - platform components not installed"
echo "$BUILD_OUTPUT" > /tmp/xcodebuild_device.log
log_info "Check build log: /tmp/xcodebuild_device.log"
BUILD_DEVICE=false
+ elif echo "$BUILD_OUTPUT" | grep -q "BUILD FAILED"; then
+ log_warn "iOS device build failed"
+ log_info ""
+ log_info "=== DEVICE BUILD ERRORS ==="
+ echo "$BUILD_OUTPUT" | grep -E "(error:|warning:|BUILD FAILED)"
+ echo "$BUILD_OUTPUT" > /tmp/xcodebuild_device.log
+ log_info ""
+ log_info "Full build log saved to: /tmp/xcodebuild_device.log"
+ log_info "View full log: cat /tmp/xcodebuild_device.log"
+ log_info "Falling back to simulator build..."
+ BUILD_DEVICE=false
elif echo "$BUILD_OUTPUT" | grep -q "BUILD SUCCEEDED"; then
log_info "✓ iOS device build completed"
+ elif [ $BUILD_EXIT_CODE -ne 0 ]; then
+ log_warn "iOS device build failed (exit code: $BUILD_EXIT_CODE)"
+ log_info ""
+ log_info "=== DEVICE BUILD ERRORS ==="
+ echo "$BUILD_OUTPUT" | grep -E "(error:|warning:|BUILD FAILED)"
+ echo "$BUILD_OUTPUT" > /tmp/xcodebuild_device.log
+ log_info ""
+ log_info "Full build log saved to: /tmp/xcodebuild_device.log"
+ log_info "View full log: cat /tmp/xcodebuild_device.log"
+ log_info "Falling back to simulator build..."
+ BUILD_DEVICE=false
else
log_warn "iOS device build completed with warnings"
echo "$BUILD_OUTPUT" > /tmp/xcodebuild_device.log
@@ -470,11 +524,31 @@ build_ios() {
CODE_SIGNING_ALLOWED=NO \
2>&1)
+ SIMULATOR_EXIT_CODE=$?
+
+ # Save full output to log file
+ echo "$SIMULATOR_BUILD_OUTPUT" > /tmp/xcodebuild_simulator.log
+
if echo "$SIMULATOR_BUILD_OUTPUT" | grep -q "BUILD SUCCEEDED"; then
log_info "✓ iOS simulator build completed successfully"
elif echo "$SIMULATOR_BUILD_OUTPUT" | grep -q "error:"; then
log_error "iOS simulator build failed"
- echo "$SIMULATOR_BUILD_OUTPUT" | grep -E "(error:|warning:)" | head -20
+ log_info ""
+ log_info "Full error output:"
+ echo "$SIMULATOR_BUILD_OUTPUT" | grep -E "(error:|warning:)"
+ log_info ""
+ log_info "Full build log saved to: /tmp/xcodebuild_simulator.log"
+ log_info "View full log: cat /tmp/xcodebuild_simulator.log"
+ log_info "View errors only: grep -E '(error:|warning:)' /tmp/xcodebuild_simulator.log"
+ exit 1
+ elif [ $SIMULATOR_EXIT_CODE -ne 0 ]; then
+ log_error "iOS simulator build failed (exit code: $SIMULATOR_EXIT_CODE)"
+ log_info ""
+ log_info "Build output (last 50 lines):"
+ echo "$SIMULATOR_BUILD_OUTPUT" | tail -50
+ log_info ""
+ log_info "Full build log saved to: /tmp/xcodebuild_simulator.log"
+ log_info "View full log: cat /tmp/xcodebuild_simulator.log"
exit 1
else
log_warn "iOS simulator build completed with warnings"