diff --git a/README.md b/README.md index 4783585..6b8b5d6 100644 --- a/README.md +++ b/README.md @@ -1,510 +1,143 @@ # Daily Notification Plugin for Capacitor -A powerful Capacitor plugin for scheduling and managing daily notifications with advanced features like timezone support, offline capabilities, and retry logic. +A Capacitor plugin for scheduling and managing daily notifications with advanced features and robust error handling. ## Features -- Schedule daily notifications at specific times -- Support for multiple notification schedules +- Schedule daily notifications with custom time and content +- Support for different priority levels - Timezone-aware scheduling -- Offline support with content caching -- Retry logic with exponential backoff -- Custom notification content handlers -- Event-based notification handling -- Comprehensive settings management -- TypeScript support with full type definitions +- Offline support with caching +- Comprehensive permission handling +- Automatic retry logic +- Detailed notification status tracking +- Configurable settings management ## Installation ```bash -npm install @timesafari/daily-notification-plugin +npm install daily-notification-plugin +npx cap sync ``` +## iOS Setup + +No additional setup required for iOS. The plugin automatically handles all necessary configurations. + ## Usage ### Basic Usage ```typescript -import { DailyNotification } from '@timesafari/daily-notification-plugin'; - -const plugin = new DailyNotification(); +import { DailyNotification } from 'daily-notification-plugin'; // Schedule a daily notification -await plugin.scheduleDailyNotification({ +await DailyNotification.scheduleDailyNotification({ url: 'https://api.example.com/updates', time: '09:00', title: 'Daily Update', - body: 'Your daily content is ready!', - sound: true, - priority: 'high' + body: 'Your daily update is ready' }); +``` + +### Advanced Features -// Get notification status -const status = await plugin.getNotificationStatus(); -console.log('Next notification:', status.nextNotificationTime); +#### Custom Priority -// Handle notification events -plugin.on('notification', (event) => { - console.log('Notification received:', event.detail); +```typescript +await DailyNotification.scheduleDailyNotification({ + url: 'https://api.example.com/updates', + time: '09:00', + priority: 'high' }); ``` -### Advanced Usage +#### Timezone Support ```typescript -// Multiple schedules with different timezones -const schedules = [ - { - url: 'https://api.example.com/morning', - time: '09:00', - timezone: 'America/New_York', - title: 'Morning Update' - }, - { - url: 'https://api.example.com/evening', - time: '18:00', - timezone: 'Europe/London', - title: 'Evening Update' - } -]; - -for (const schedule of schedules) { - await plugin.scheduleDailyNotification(schedule); -} - -// Offline support with caching -await plugin.scheduleDailyNotification({ +await DailyNotification.scheduleDailyNotification({ url: 'https://api.example.com/updates', - time: '10:00', - offlineFallback: true, - cacheDuration: 3600, // 1 hour - contentHandler: async (response) => { - const data = await response.json(); - return { - title: data.title, - body: data.content, - data: data.metadata - }; - } + time: '09:00', + timezone: 'America/New_York' }); +``` + +#### Check Notification Status -// Update settings -await plugin.updateSettings({ - time: '11:00', +```typescript +const status = await DailyNotification.getNotificationStatus(); +console.log('Notification status:', status); +``` + +#### Update Settings + +```typescript +await DailyNotification.updateSettings({ sound: true, priority: 'high', - timezone: 'America/Chicago' + timezone: 'America/Los_Angeles' }); ``` -## API Reference +## API Documentation ### Methods -#### `scheduleDailyNotification(options: NotificationOptions): Promise` +#### scheduleDailyNotification(options: ScheduleOptions) +Schedule a new daily notification. -Schedules a daily notification with the specified options. +#### getLastNotification() +Get information about the last delivered notification. -```typescript -interface NotificationOptions { - url: string; - time: string; // "HH:mm" format - title?: string; - body?: string; - sound?: boolean; - vibrate?: boolean; - priority?: 'low' | 'normal' | 'high'; - retryCount?: number; - retryInterval?: number; - cacheDuration?: number; - headers?: Record; - offlineFallback?: boolean; - timezone?: string; - contentHandler?: (response: Response) => Promise<{ - title: string; - body: string; - data?: any; - }>; -} -``` - -#### `getLastNotification(): Promise` - -Retrieves the last notification that was delivered. - -#### `cancelAllNotifications(): Promise` +#### cancelAllNotifications() +Cancel all pending notifications. -Cancels all scheduled notifications. +#### getNotificationStatus() +Get current notification status and settings. -#### `getNotificationStatus(): Promise` +#### updateSettings(settings: NotificationSettings) +Update notification settings. -Gets the current status of notifications. +#### checkPermissions() +Check current notification permissions. -#### `updateSettings(settings: NotificationSettings): Promise` +#### requestPermissions() +Request notification permissions. -Updates notification settings. +### Types ```typescript -interface NotificationSettings { - time?: string; +interface ScheduleOptions { + url: string; + time: string; + title?: string; + body?: string; sound?: boolean; - vibrate?: boolean; - priority?: 'low' | 'normal' | 'high'; + priority?: 'high' | 'default' | 'low'; timezone?: string; } -``` - -### Events -The plugin emits the following events: - -- `notification`: Fired when a notification is received or interacted with - -```typescript -interface NotificationEvent extends Event { - detail: { - id: string; - action: string; - data?: any; - }; +interface NotificationSettings { + sound?: boolean; + priority?: string; + timezone?: string; } ``` -## Testing - -The plugin includes comprehensive tests covering: - -- Basic functionality -- Multiple schedules -- Timezone handling -- Offline support -- Retry logic -- Event handling -- Settings management - -Run tests with: - -```bash -npm test -``` - -## Security Considerations - -- All network requests use HTTPS -- Content is validated before display -- Sensitive data is not stored in logs -- Permissions are properly managed -- Input validation is performed on all methods - -## Contributing - -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request - -## License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - -## Author - -Matthew Raymer - -## Platform-Specific Implementation - -### Android Implementation - -- Uses `WorkManager` for periodic data fetching -- Implements `AlarmManager` for precise notification scheduling -- Stores data in `SharedPreferences` -- Handles Doze mode and battery optimizations - -### iOS - -- Utilizes `BGTaskScheduler` for background fetches -- Implements `UNUserNotificationCenter` for notifications -- Stores data in `UserDefaults` -- Respects system background execution limits - -## Permissions - -### Android Permissions - -Required permissions in `AndroidManifest.xml`: - -```xml - - - -``` - -### iOS Implementation - -- Notification permissions (requested at runtime) -- Background App Refresh capability (enabled in Xcode) - ## Error Handling -The plugin implements comprehensive error handling: - -- Network failure retry logic with exponential backoff -- Fallback to cached content when fetching fails -- Detailed error logging and reporting -- Graceful degradation when permissions are denied - -## Best Practices - -### Content Management - -- Keep notification content concise and actionable -- Use clear, engaging titles under 50 characters -- Limit notification body to 2-3 lines -- Include a clear call-to-action when appropriate - -### Network Optimization - -- Implement proper caching headers on your API -- Use compression for network responses -- Keep payload size under 4KB for optimal performance -- Implement rate limiting on your API endpoints - -### Battery Considerations - -- Schedule notifications during active hours -- Avoid excessive background fetches -- Use appropriate fetch intervals (minimum 15 minutes) -- Implement smart retry strategies - -### User Experience - -- Request notification permissions at an appropriate time -- Provide clear value proposition for notifications -- Allow users to customize notification timing -- Implement proper error messaging for users - -### Security - -- Always use HTTPS for API endpoints -- Implement proper API authentication -- Sanitize notification content -- Follow platform-specific security guidelines - -### Testing - -- Test notifications in various app states -- Verify behavior with different network conditions -- Test on multiple device types and OS versions -- Implement proper error logging for debugging - -## Development Setup - -### Prerequisites - -1. Node.js 14 or higher -2. Java 11 or higher -3. Android Studio and Android SDK -4. Xcode (for iOS development, macOS only) -5. CocoaPods (for iOS development) - -### Environment Setup - -1. Clone the repository: - -```bash -git clone https://github.com/yourusername/capacitor-daily-notification.git -cd capacitor-daily-notification -``` - -2. Install dependencies: - -```bash -npm install -``` - -This will: - -- Install Node.js dependencies -- Check your development environment -- Set up native build environments -- Install platform-specific dependencies - -### Building the Plugin - -#### TypeScript Build - -```bash -# Build TypeScript code -npm run build - -# Watch mode for development -npm run watch -``` - -#### Android Build - -```bash -# Build Android library -npm run build:android - -# Run Android tests -npm run test:android -``` - -The Android build will: - -1. Compile the TypeScript code -2. Build the Android library -3. Generate an AAR file in `android/build/outputs/aar/` - -#### iOS Build - -```bash -# Build iOS library -npm run build:ios - -# Run iOS tests -npm run test:ios -``` - -The iOS build will: - -1. Compile the TypeScript code -2. Install CocoaPods dependencies -3. Build the iOS framework - -### Using in a Capacitor App - -1. Install the plugin in your Capacitor app: - -```bash -npm install capacitor-daily-notification -``` - -2. Add to your Android app's `android/app/build.gradle`: - -```gradle -dependencies { - implementation project(':daily-notification') -} -``` - -3. Add to your iOS app's `ios/App/Podfile`: - -```ruby -pod 'CapacitorDailyNotification', :path => '../node_modules/capacitor-daily-notification' -``` - -4. Sync native projects: - -```bash -npx cap sync -``` - -### Troubleshooting - -#### Android Issues - -1. Gradle Sync Failed - -```bash -cd android -./gradlew clean -./gradlew --refresh-dependencies -``` - -2. Missing Android SDK - -- Set ANDROID_HOME environment variable -- Install required SDK components via Android Studio - -3. Build Errors - -- Check Android Studio for detailed error messages -- Ensure all required SDK components are installed -- Verify Gradle version compatibility - -#### iOS Issues - -1. Pod Install Failed - -```bash -cd ios -pod deintegrate -pod cache clean --all -pod install -``` - -2. Xcode Build Errors - -- Open Xcode project in Xcode -- Check build settings -- Verify deployment target matches requirements - -3. Missing Dependencies - -- Ensure CocoaPods is installed -- Run `pod setup` to update CocoaPods repos - -### Development Workflow - -1. Make changes to TypeScript code -2. Run `npm run build` to compile -3. Run `npm run build:native` to build native code -4. Test changes: - - Android: `npm run test:android` - - iOS: `npm run test:ios` -5. Run full validation: - - Android: `npm run validate` - - iOS: `npm run validate:ios` - -### Continuous Integration - -The plugin includes pre-commit hooks and CI configurations: - -1. Pre-commit checks: - - TypeScript compilation - - Linting - - Native build verification - -2. CI pipeline: - - Environment validation - - TypeScript build - - Native builds - - Unit tests - - Integration tests +The plugin provides detailed error messages for various scenarios: +- Invalid parameters +- Permission issues +- Scheduling failures +- Invalid time formats +- Invalid timezone identifiers +- Invalid priority values ## Contributing -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request +Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository. ## License -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - -## Author - -Matthew Raymer - -## Plugin Security - -This plugin follows security best practices: - -- Secure storage of sensitive data -- HTTPS-only network requests -- Permission-based access control -- Regular security audits -- No sensitive data in logs - -## Support - -For support, please open an issue in the GitHub repository or contact the maintainers. - -## Changelog - -See [CHANGELOG.md](CHANGELOG.md) for a list of changes and version history. +MIT License - see LICENSE file for details diff --git a/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java b/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java index eeed412..954a478 100644 --- a/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java +++ b/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java @@ -13,6 +13,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; @@ -35,6 +36,8 @@ public class DailyNotificationPlugin extends Plugin { private NotificationManager notificationManager; private AlarmManager alarmManager; private Context context; + private SharedPreferences settings; + private static final String SETTINGS_KEY = "daily_notification_settings"; @Override public void load() { @@ -42,6 +45,7 @@ public class DailyNotificationPlugin extends Plugin { notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); createNotificationChannel(); + settings = context.getSharedPreferences(SETTINGS_KEY, Context.MODE_PRIVATE); } private void createNotificationChannel() { @@ -154,7 +158,18 @@ public class DailyNotificationPlugin extends Plugin { @PluginMethod public void updateSettings(PluginCall call) { - // TODO: Implement settings update + SharedPreferences.Editor editor = settings.edit(); + if (call.hasOption("timezone")) { + String timezone = call.getString("timezone"); + if (TimeZone.getTimeZone(timezone) != null) { + editor.putString("timezone", timezone); + } else { + call.reject("Invalid timezone"); + return; + } + } + // Add other settings... + editor.apply(); call.resolve(); } } \ No newline at end of file diff --git a/ios/DailyNotificationPlugin.podspec b/ios/DailyNotificationPlugin.podspec new file mode 100644 index 0000000..8da466a --- /dev/null +++ b/ios/DailyNotificationPlugin.podspec @@ -0,0 +1,16 @@ +Pod::Spec.new do |s| + s.name = 'DailyNotificationPlugin' + s.version = '1.0.0' + s.summary = 'Daily notification plugin for Capacitor' + s.license = 'MIT' + s.homepage = 'https://github.com/timesafari/daily-notification-plugin' + s.author = 'TimeSafari' + s.source = { :git => 'https://github.com/timesafari/daily-notification-plugin.git', :tag => s.version.to_s } + s.source_files = 'Plugin/**/*.{swift,h,m,c,cc,mm,cpp}' + s.ios.deployment_target = '13.0' + s.dependency 'Capacitor' + s.dependency 'CapacitorCordova' + s.swift_version = '5.1' + s.module_name = 'DailyNotificationPlugin' + s.static_framework = true +end \ No newline at end of file diff --git a/ios/DailyNotificationPlugin.xcodeproj/project.pbxproj b/ios/DailyNotificationPlugin.xcodeproj/project.pbxproj new file mode 100644 index 0000000..31d66ed --- /dev/null +++ b/ios/DailyNotificationPlugin.xcodeproj/project.pbxproj @@ -0,0 +1,570 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 0C773ACB32780E104F9F24C8 /* DailyNotificationPlugin.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3323D7F1B3ED08FAA559D317 /* DailyNotificationPlugin.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 2B7E978FB6B43664754FC9BB /* DailyNotificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6495D32429F76744DC98B457 /* DailyNotificationTests.swift */; }; + 3C8161B717E367A8B88111BF /* DailyNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC45ABFC1A903AE6233B13F /* DailyNotificationConfig.swift */; }; + 5898E89B868436827E1E06DF /* DailyNotificationPlugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3323D7F1B3ED08FAA559D317 /* DailyNotificationPlugin.framework */; }; + 6C1E173CC70849A66F85B12C /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 2DDEB96E881144AB9DBCDF17 /* README.md */; }; + 74067B7FB473F9455DABAA30 /* Pods_DailyNotificationPlugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BD2A2F1CA91755E648A5D57 /* Pods_DailyNotificationPlugin.framework */; }; + 8640E8860A5334043F24DC54 /* Pods_DailyNotificationPluginTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A49F9F065B6559C77DE3BD5 /* Pods_DailyNotificationPluginTests.framework */; }; + 92CCC02492ECD5CADABE7BC4 /* DailyNotificationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC43315277A65E01BD1B352F /* DailyNotificationError.swift */; }; + C194055B816EA1C2DCF17BD2 /* DailyNotificationPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = A645658475F4138A9256D485 /* DailyNotificationPlugin.swift */; }; + DBCD22D6FACC2C730D7C2911 /* DailyNotificationLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 467AB34F611231718368D768 /* DailyNotificationLogger.swift */; }; + F86B65B48D5B072F30F76479 /* index.ts in Resources */ = {isa = PBXBuildFile; fileRef = A73A4B8720F98987381BAECF /* index.ts */; }; + FB9C99C0B426E0109ED1218C /* DailyNotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9237F98BBE39C5667CCCF78C /* DailyNotificationConstants.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + AE616C66C793C36141ED183E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 006593AF8D5F74184E8E7C52 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 257C4D3AD8FBFC25B796808A; + remoteInfo = DailyNotificationPlugin; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 4943F65747C3ABB97AEBDDB9 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 0C773ACB32780E104F9F24C8 /* DailyNotificationPlugin.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0A49F9F065B6559C77DE3BD5 /* Pods_DailyNotificationPluginTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DailyNotificationPluginTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2DDEB96E881144AB9DBCDF17 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 3323D7F1B3ED08FAA559D317 /* DailyNotificationPlugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DailyNotificationPlugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3BD2A2F1CA91755E648A5D57 /* Pods_DailyNotificationPlugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DailyNotificationPlugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 40B0BB9ACA2D2A1BEB4DF9A2 /* Pods-DailyNotificationPluginTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DailyNotificationPluginTests.debug.xcconfig"; path = "Target Support Files/Pods-DailyNotificationPluginTests/Pods-DailyNotificationPluginTests.debug.xcconfig"; sourceTree = ""; }; + 4494D62E3E3D3A7EFAE5270C /* Pods-DailyNotificationPlugin.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DailyNotificationPlugin.debug.xcconfig"; path = "Target Support Files/Pods-DailyNotificationPlugin/Pods-DailyNotificationPlugin.debug.xcconfig"; sourceTree = ""; }; + 467AB34F611231718368D768 /* DailyNotificationLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyNotificationLogger.swift; sourceTree = ""; }; + 6495D32429F76744DC98B457 /* DailyNotificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyNotificationTests.swift; sourceTree = ""; }; + 77711E259A9C3D73936E2780 /* Pods-DailyNotificationPlugin.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DailyNotificationPlugin.release.xcconfig"; path = "Target Support Files/Pods-DailyNotificationPlugin/Pods-DailyNotificationPlugin.release.xcconfig"; sourceTree = ""; }; + 7DA0C384AE2BD3E27E1CA95A /* Pods-DailyNotificationPluginTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DailyNotificationPluginTests.release.xcconfig"; path = "Target Support Files/Pods-DailyNotificationPluginTests/Pods-DailyNotificationPluginTests.release.xcconfig"; sourceTree = ""; }; + 9237F98BBE39C5667CCCF78C /* DailyNotificationConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyNotificationConstants.swift; sourceTree = ""; }; + A645658475F4138A9256D485 /* DailyNotificationPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyNotificationPlugin.swift; sourceTree = ""; }; + A73A4B8720F98987381BAECF /* index.ts */ = {isa = PBXFileReference; path = index.ts; sourceTree = ""; }; + AA32A2FDCE5869E99D5888F0 /* DailyNotificationPluginTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = DailyNotificationPluginTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + DC43315277A65E01BD1B352F /* DailyNotificationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyNotificationError.swift; sourceTree = ""; }; + FFC45ABFC1A903AE6233B13F /* DailyNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyNotificationConfig.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 55585DA18B4D813764BADC93 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 74067B7FB473F9455DABAA30 /* Pods_DailyNotificationPlugin.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8D6E11ADD3F7437A18DDD332 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5898E89B868436827E1E06DF /* DailyNotificationPlugin.framework in Frameworks */, + 8640E8860A5334043F24DC54 /* Pods_DailyNotificationPluginTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1E538212868D96F5FE5949D3 /* Plugin */ = { + isa = PBXGroup; + children = ( + FFC45ABFC1A903AE6233B13F /* DailyNotificationConfig.swift */, + 9237F98BBE39C5667CCCF78C /* DailyNotificationConstants.swift */, + DC43315277A65E01BD1B352F /* DailyNotificationError.swift */, + 467AB34F611231718368D768 /* DailyNotificationLogger.swift */, + A645658475F4138A9256D485 /* DailyNotificationPlugin.swift */, + A73A4B8720F98987381BAECF /* index.ts */, + 2DDEB96E881144AB9DBCDF17 /* README.md */, + ); + path = Plugin; + sourceTree = ""; + }; + 28D6F94971201D3DB74C25C2 = { + isa = PBXGroup; + children = ( + 1E538212868D96F5FE5949D3 /* Plugin */, + 3233C506859453B03BDE86D8 /* Tests */, + E84F19FCBB1C18FE94E42EC8 /* Products */, + FA2E2F2AFEF5A51D9DCAFC04 /* Pods */, + ADB432E27D513A3C69B9A1A3 /* Frameworks */, + ); + sourceTree = ""; + }; + 3233C506859453B03BDE86D8 /* Tests */ = { + isa = PBXGroup; + children = ( + 6495D32429F76744DC98B457 /* DailyNotificationTests.swift */, + ); + path = Tests; + sourceTree = ""; + }; + ADB432E27D513A3C69B9A1A3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3BD2A2F1CA91755E648A5D57 /* Pods_DailyNotificationPlugin.framework */, + 0A49F9F065B6559C77DE3BD5 /* Pods_DailyNotificationPluginTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E84F19FCBB1C18FE94E42EC8 /* Products */ = { + isa = PBXGroup; + children = ( + 3323D7F1B3ED08FAA559D317 /* DailyNotificationPlugin.framework */, + AA32A2FDCE5869E99D5888F0 /* DailyNotificationPluginTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + FA2E2F2AFEF5A51D9DCAFC04 /* Pods */ = { + isa = PBXGroup; + children = ( + 4494D62E3E3D3A7EFAE5270C /* Pods-DailyNotificationPlugin.debug.xcconfig */, + 77711E259A9C3D73936E2780 /* Pods-DailyNotificationPlugin.release.xcconfig */, + 40B0BB9ACA2D2A1BEB4DF9A2 /* Pods-DailyNotificationPluginTests.debug.xcconfig */, + 7DA0C384AE2BD3E27E1CA95A /* Pods-DailyNotificationPluginTests.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 257C4D3AD8FBFC25B796808A /* DailyNotificationPlugin */ = { + isa = PBXNativeTarget; + buildConfigurationList = A48909A0B23F2E1853CA7243 /* Build configuration list for PBXNativeTarget "DailyNotificationPlugin" */; + buildPhases = ( + EFC33670012401F59F542C6C /* [CP] Check Pods Manifest.lock */, + B64C03AB42C5F59F0C2AC34C /* Sources */, + F64E92967F4F9B8AB5FB3FF0 /* Resources */, + 55585DA18B4D813764BADC93 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DailyNotificationPlugin; + productName = DailyNotificationPlugin; + productReference = 3323D7F1B3ED08FAA559D317 /* DailyNotificationPlugin.framework */; + productType = "com.apple.product-type.framework"; + }; + D5D84A92030AF309D12F0C04 /* DailyNotificationPluginTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 29C4C700B6D1011122DEB3D1 /* Build configuration list for PBXNativeTarget "DailyNotificationPluginTests" */; + buildPhases = ( + 2A3C79169070A704F233230B /* [CP] Check Pods Manifest.lock */, + 55FB7A11B0C278223BAC8576 /* Sources */, + 8D6E11ADD3F7437A18DDD332 /* Frameworks */, + 4943F65747C3ABB97AEBDDB9 /* Embed Frameworks */, + 7B305C495763B32DED093F95 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + E48942294A3D346D124D468C /* PBXTargetDependency */, + ); + name = DailyNotificationPluginTests; + productName = DailyNotificationPluginTests; + productReference = AA32A2FDCE5869E99D5888F0 /* DailyNotificationPluginTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 006593AF8D5F74184E8E7C52 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + TargetAttributes = { + 257C4D3AD8FBFC25B796808A = { + DevelopmentTeam = ""; + }; + D5D84A92030AF309D12F0C04 = { + DevelopmentTeam = ""; + }; + }; + }; + buildConfigurationList = E3A89DF03E1CDA836997EEC6 /* Build configuration list for PBXProject "DailyNotificationPlugin" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 28D6F94971201D3DB74C25C2; + productRefGroup = E84F19FCBB1C18FE94E42EC8 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 257C4D3AD8FBFC25B796808A /* DailyNotificationPlugin */, + D5D84A92030AF309D12F0C04 /* DailyNotificationPluginTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + F64E92967F4F9B8AB5FB3FF0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6C1E173CC70849A66F85B12C /* README.md in Resources */, + F86B65B48D5B072F30F76479 /* index.ts in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 2A3C79169070A704F233230B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-DailyNotificationPluginTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 7B305C495763B32DED093F95 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-DailyNotificationPluginTests/Pods-DailyNotificationPluginTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-DailyNotificationPluginTests/Pods-DailyNotificationPluginTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DailyNotificationPluginTests/Pods-DailyNotificationPluginTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + EFC33670012401F59F542C6C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-DailyNotificationPlugin-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 55FB7A11B0C278223BAC8576 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2B7E978FB6B43664754FC9BB /* DailyNotificationTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B64C03AB42C5F59F0C2AC34C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3C8161B717E367A8B88111BF /* DailyNotificationConfig.swift in Sources */, + FB9C99C0B426E0109ED1218C /* DailyNotificationConstants.swift in Sources */, + 92CCC02492ECD5CADABE7BC4 /* DailyNotificationError.swift in Sources */, + DBCD22D6FACC2C730D7C2911 /* DailyNotificationLogger.swift in Sources */, + C194055B816EA1C2DCF17BD2 /* DailyNotificationPlugin.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + E48942294A3D346D124D468C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 257C4D3AD8FBFC25B796808A /* DailyNotificationPlugin */; + targetProxy = AE616C66C793C36141ED183E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 1B945D5C7C74FADDDFCFBE5B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 77711E259A9C3D73936E2780 /* Pods-DailyNotificationPlugin.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.timesafari.dailynotification; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 34E9AA395681C624E34D67AC /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4494D62E3E3D3A7EFAE5270C /* Pods-DailyNotificationPlugin.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.timesafari.dailynotification; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 67AF4EA7655D29A052EF8B83 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 8C3734FD07E9594E1FAA5E80 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7DA0C384AE2BD3E27E1CA95A /* Pods-DailyNotificationPluginTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.timesafari.dailynotification.tests; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 9A90FB54CF53D768F76987E3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40B0BB9ACA2D2A1BEB4DF9A2 /* Pods-DailyNotificationPluginTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.timesafari.dailynotification.tests; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + E64E663501BF0EF5E09805E2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 29C4C700B6D1011122DEB3D1 /* Build configuration list for PBXNativeTarget "DailyNotificationPluginTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A90FB54CF53D768F76987E3 /* Debug */, + 8C3734FD07E9594E1FAA5E80 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + A48909A0B23F2E1853CA7243 /* Build configuration list for PBXNativeTarget "DailyNotificationPlugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 34E9AA395681C624E34D67AC /* Debug */, + 1B945D5C7C74FADDDFCFBE5B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + E3A89DF03E1CDA836997EEC6 /* Build configuration list for PBXProject "DailyNotificationPlugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 67AF4EA7655D29A052EF8B83 /* Debug */, + E64E663501BF0EF5E09805E2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 006593AF8D5F74184E8E7C52 /* Project object */; +} diff --git a/ios/DailyNotificationPlugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/DailyNotificationPlugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/DailyNotificationPlugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/DailyNotificationPlugin.xcodeproj/xcshareddata/xcschemes/DailyNotificationPlugin.xcscheme b/ios/DailyNotificationPlugin.xcodeproj/xcshareddata/xcschemes/DailyNotificationPlugin.xcscheme new file mode 100644 index 0000000..1501226 --- /dev/null +++ b/ios/DailyNotificationPlugin.xcodeproj/xcshareddata/xcschemes/DailyNotificationPlugin.xcscheme @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/DailyNotificationPlugin.xcodeproj/xcshareddata/xcschemes/DailyNotificationPluginTests.xcscheme b/ios/DailyNotificationPlugin.xcodeproj/xcshareddata/xcschemes/DailyNotificationPluginTests.xcscheme new file mode 100644 index 0000000..9cacfd5 --- /dev/null +++ b/ios/DailyNotificationPlugin.xcodeproj/xcshareddata/xcschemes/DailyNotificationPluginTests.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/DailyNotificationPlugin.xcworkspace/contents.xcworkspacedata b/ios/DailyNotificationPlugin.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..814246a --- /dev/null +++ b/ios/DailyNotificationPlugin.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ios/DailyNotificationPlugin.xcworkspace/xcuserdata/cloud.xcuserdatad/UserInterfaceState.xcuserstate b/ios/DailyNotificationPlugin.xcworkspace/xcuserdata/cloud.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..51b5014 Binary files /dev/null and b/ios/DailyNotificationPlugin.xcworkspace/xcuserdata/cloud.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ios/Plugin/DailyNotificationConfig.swift b/ios/Plugin/DailyNotificationConfig.swift new file mode 100644 index 0000000..5d5a6f4 --- /dev/null +++ b/ios/Plugin/DailyNotificationConfig.swift @@ -0,0 +1,31 @@ +/** + * DailyNotificationConfig.swift + * Daily Notification Plugin for Capacitor + * + * Provides configuration options for the notification plugin + */ + +import Foundation + +/// Configuration options for the DailyNotification plugin +/// +/// This singleton structure provides configurable options that can be modified +/// to customize the plugin's behavior. +public struct DailyNotificationConfig { + /// Shared instance for singleton access + public static var shared = DailyNotificationConfig() + + /// Maximum number of notifications that can be scheduled per day + public var maxNotificationsPerDay = 10 + + /// Default timezone for notifications when none is specified + public var defaultTimeZone = TimeZone.current + + /// Whether debug logging is enabled + public var loggingEnabled = true + + /// Number of days to retain delivered notifications + public var retentionDays = 7 + + private init() {} +} \ No newline at end of file diff --git a/ios/Plugin/DailyNotificationConstants.swift b/ios/Plugin/DailyNotificationConstants.swift new file mode 100644 index 0000000..4b38703 --- /dev/null +++ b/ios/Plugin/DailyNotificationConstants.swift @@ -0,0 +1,52 @@ +/** + * DailyNotificationConstants.swift + * Daily Notification Plugin for Capacitor + * + * Defines constant values used throughout the notification plugin + */ + +import Foundation + +/// Constants used throughout the DailyNotification plugin +/// +/// This structure provides centralized access to all constant values used in the plugin, +/// making it easier to maintain and update common values. +public struct DailyNotificationConstants { + /// Default notification title when none is provided + public static let defaultTitle = "Daily Notification" + + /// Default notification body when none is provided + public static let defaultBody = "Your daily update is ready" + + /// Prefix used for notification identifiers + public static let notificationIdentifierPrefix = "daily-notification-" + + /// Name of the event emitted for notification interactions + public static let eventName = "notification" + + /// Default settings for notifications + public struct Settings { + /// Whether sound is enabled by default + public static let defaultSound = true + + /// Default priority level for notifications + public static let defaultPriority = "default" + + /// Default number of retry attempts + public static let defaultRetryCount = 3 + + /// Default interval between retries (in milliseconds) + public static let defaultRetryInterval = 1000 + } + + /// Keys used in plugin method calls + public struct Keys { + public static let url = "url" + public static let time = "time" + public static let title = "title" + public static let body = "body" + public static let sound = "sound" + public static let priority = "priority" + public static let timezone = "timezone" + } +} \ No newline at end of file diff --git a/ios/Plugin/DailyNotificationError.swift b/ios/Plugin/DailyNotificationError.swift new file mode 100644 index 0000000..47ab9d3 --- /dev/null +++ b/ios/Plugin/DailyNotificationError.swift @@ -0,0 +1,50 @@ +/** + * DailyNotificationError.swift + * Daily Notification Plugin for Capacitor + * + * Defines error types and handling for the notification plugin + */ + +import Foundation + +/// Represents possible errors that can occur within the DailyNotification plugin +/// +/// These errors provide specific failure cases and associated messages for better +/// error handling and debugging in the notification scheduling process. +public enum DailyNotificationError: Error { + /// Thrown when required parameters are missing or invalid + case invalidParameters(String) + + /// Thrown when notification permissions have not been granted + case permissionDenied + + /// Thrown when notification scheduling fails + case schedulingFailed(String) + + /// Thrown when time format is invalid (should be HH:mm) + case invalidTimeFormat + + /// Thrown when provided timezone identifier is invalid + case invalidTimezone + + /// Thrown when priority value is not one of: high, default, low + case invalidPriority + + /// Human-readable error messages for each error case + public var message: String { + switch self { + case .invalidParameters(let detail): + return "Invalid parameters: \(detail)" + case .permissionDenied: + return "Notification permissions not granted" + case .schedulingFailed(let reason): + return "Failed to schedule notification: \(reason)" + case .invalidTimeFormat: + return "Invalid time format. Expected HH:mm" + case .invalidTimezone: + return "Invalid timezone identifier" + case .invalidPriority: + return "Invalid priority value. Expected 'high', 'default', or 'low'" + } + } +} \ No newline at end of file diff --git a/ios/Plugin/DailyNotificationLogger.swift b/ios/Plugin/DailyNotificationLogger.swift new file mode 100644 index 0000000..12a4edc --- /dev/null +++ b/ios/Plugin/DailyNotificationLogger.swift @@ -0,0 +1,44 @@ +/** + * DailyNotificationLogger.swift + * Daily Notification Plugin for Capacitor + * + * Provides logging functionality for the notification plugin + */ + +import Foundation + +/// Logger utility for the DailyNotification plugin +/// +/// Provides structured logging capabilities with different severity levels +/// and automatic file/line/function information. +public class DailyNotificationLogger { + /// Shared instance for singleton access + public static let shared = DailyNotificationLogger() + + /// Available logging levels + public enum Level: String { + case debug, info, warning, error + } + + private init() {} + + /// Log a message with the specified severity level + /// - Parameters: + /// - level: The severity level of the log + /// - message: The message to log + /// - file: The file where the log was called (automatic) + /// - function: The function where the log was called (automatic) + /// - line: The line number where the log was called (automatic) + public func log( + _ level: Level, + _ message: String, + file: String = #file, + function: String = #function, + line: Int = #line + ) { + #if DEBUG + let fileName = (file as NSString).lastPathComponent + print("[\(level.rawValue.uppercased())] [\(fileName):\(line)] \(function): \(message)") + #endif + } +} \ No newline at end of file diff --git a/ios/Plugin/DailyNotificationPlugin.swift b/ios/Plugin/DailyNotificationPlugin.swift index cf2f109..b7670f4 100644 --- a/ios/Plugin/DailyNotificationPlugin.swift +++ b/ios/Plugin/DailyNotificationPlugin.swift @@ -9,10 +9,29 @@ import Foundation import Capacitor import UserNotifications +/// Represents the main plugin class for handling daily notifications +/// +/// This plugin provides functionality for scheduling and managing daily notifications +/// on iOS devices using the UserNotifications framework. @objc(DailyNotificationPlugin) public class DailyNotificationPlugin: CAPPlugin { private let notificationCenter = UNUserNotificationCenter.current() + private var settings: [String: Any] = [ + "sound": true, + "priority": "default", + "retryCount": 3, + "retryInterval": 1000 + ] + + private static let CHANNEL_ID = "daily_notification_channel" + private static let CHANNEL_NAME = "Daily Notifications" + private static let CHANNEL_DESCRIPTION = "Daily notification updates" + + /// Schedules a new daily notification + /// - Parameter call: The plugin call containing notification parameters + /// - Returns: Void + /// - Throws: DailyNotificationError @objc func scheduleDailyNotification(_ call: CAPPluginCall) { guard let url = call.getString("url"), let time = call.getString("time") else { @@ -39,25 +58,51 @@ public class DailyNotificationPlugin: CAPPlugin { // Set priority if let priority = call.getString("priority") { - switch priority { - case "high": - content.interruptionLevel = .timeSensitive - case "low": - content.interruptionLevel = .passive - default: - content.interruptionLevel = .active + if #available(iOS 15.0, *) { + switch priority { + case "high": + content.interruptionLevel = .timeSensitive + case "low": + content.interruptionLevel = .passive + default: + content.interruptionLevel = .active + } } } + // Add to notification content setup + content.categoryIdentifier = "DAILY_NOTIFICATION" + let category = UNNotificationCategory( + identifier: "DAILY_NOTIFICATION", + actions: [], + intentIdentifiers: [], + options: .customDismissAction + ) + notificationCenter.setNotificationCategories([category]) + // Create trigger for daily notification var dateComponents = DateComponents() dateComponents.hour = hour dateComponents.minute = minute let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) + // Add check for past time and adjust to next day + let calendar = Calendar.current + var components = DateComponents() + components.hour = hour + components.minute = minute + components.second = 0 + + if let date = calendar.date(from: components), + date.timeIntervalSinceNow <= 0 { + components.day = calendar.component(.day, from: Date()) + 1 + } + // Create request + let identifier = String(format: "daily-notification-%d", (url as NSString).hash) + content.userInfo = ["url": url] let request = UNNotificationRequest( - identifier: "daily-notification-\(url)", + identifier: identifier, content: content, trigger: trigger ) @@ -92,18 +137,192 @@ public class DailyNotificationPlugin: CAPPlugin { } @objc func getNotificationStatus(_ call: CAPPluginCall) { - notificationCenter.getPendingNotificationRequests { requests in - let nextNotification = requests.first - let result: [String: Any] = [ - "nextNotificationTime": nextNotification?.trigger?.nextTriggerDate?.timeIntervalSince1970 ?? 0, - "isEnabled": true // TODO: Check system notification settings - ] - call.resolve(result) + notificationCenter.getNotificationSettings { settings in + self.notificationCenter.getPendingNotificationRequests { requests in + var result: [String: Any] = [ + "isEnabled": settings.authorizationStatus == .authorized, + "pending": requests.count + ] + + if let nextRequest = requests.first, + let trigger = nextRequest.trigger as? UNCalendarNotificationTrigger { + result["nextNotificationTime"] = trigger.nextTriggerDate()?.timeIntervalSince1970 ?? 0 + } + + // Add current settings + result["settings"] = self.settings + + call.resolve(result) + } } } @objc func updateSettings(_ call: CAPPluginCall) { - // TODO: Implement settings update - call.resolve() + if let sound = call.getBool("sound") { + settings["sound"] = sound + } + + if let priority = call.getString("priority") { + guard ["high", "default", "low"].contains(priority) else { + call.reject("Invalid priority value") + return + } + settings["priority"] = priority + } + + if let timezone = call.getString("timezone") { + guard TimeZone(identifier: timezone) != nil else { + call.reject("Invalid timezone") + return + } + settings["timezone"] = timezone + } + + // Update any existing notifications with new settings + notificationCenter.getPendingNotificationRequests { [weak self] requests in + guard let self = self else { return } + + for request in requests { + let content = request.content.mutableCopy() as! UNMutableNotificationContent + + // Update notification content based on new settings + content.sound = self.settings["sound"] as! Bool ? .default : nil + + if let priority = self.settings["priority"] as? String { + if #available(iOS 15.0, *) { + switch priority { + case "high": content.interruptionLevel = .timeSensitive + case "low": content.interruptionLevel = .passive + default: content.interruptionLevel = .active + } + } + } + + let newRequest = UNNotificationRequest( + identifier: request.identifier, + content: content, + trigger: request.trigger + ) + + self.notificationCenter.add(newRequest) + } + } + + call.resolve(settings) + } + + @objc public override func checkPermissions(_ call: CAPPluginCall) { + notificationCenter.getNotificationSettings { settings in + var result: [String: Any] = [:] + + // Convert authorization status + switch settings.authorizationStatus { + case .authorized: + result["status"] = "granted" + case .denied: + result["status"] = "denied" + case .provisional: + result["status"] = "provisional" + case .ephemeral: + result["status"] = "ephemeral" + default: + result["status"] = "unknown" + } + + // Add detailed settings + result["alert"] = settings.alertSetting == .enabled + result["badge"] = settings.badgeSetting == .enabled + result["sound"] = settings.soundSetting == .enabled + result["lockScreen"] = settings.lockScreenSetting == .enabled + result["carPlay"] = settings.carPlaySetting == .enabled + + call.resolve(result) + } + } + + @objc public override func requestPermissions(_ call: CAPPluginCall) { + let options: UNAuthorizationOptions = [.alert, .sound, .badge] + + notificationCenter.requestAuthorization(options: options) { granted, error in + if let error = error { + call.reject("Failed to request permissions: \(error.localizedDescription)") + return + } + + call.resolve([ + "granted": granted + ]) + } + } + + public override func load() { + notificationCenter.delegate = self + } + + private func isValidTime(_ time: String) -> Bool { + let timeComponents = time.split(separator: ":") + guard timeComponents.count == 2, + let hour = Int(timeComponents[0]), + let minute = Int(timeComponents[1]) else { + return false + } + return hour >= 0 && hour < 24 && minute >= 0 && minute < 60 + } + + private func isValidTimezone(_ identifier: String) -> Bool { + return TimeZone(identifier: identifier) != nil + } + + private func cleanupOldNotifications() { + let cutoffDate = Date().addingTimeInterval(-Double(DailyNotificationConfig.shared.retentionDays * 24 * 60 * 60)) + notificationCenter.getDeliveredNotifications { notifications in + let oldNotifications = notifications.filter { $0.date < cutoffDate } + self.notificationCenter.removeDeliveredNotifications(withIdentifiers: oldNotifications.map { $0.request.identifier }) + } + } + + private func setupNotificationChannel() { + // iOS doesn't use notification channels like Android + // This method is kept for API compatibility + } +} + +extension DailyNotificationPlugin: UNUserNotificationCenterDelegate { + public func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void + ) { + let notification = response.notification + let userInfo = notification.request.content.userInfo + + // Create notification event data + let eventData: [String: Any] = [ + "id": notification.request.identifier, + "title": notification.request.content.title, + "body": notification.request.content.body, + "action": response.actionIdentifier, + "data": userInfo + ] + + // Notify JavaScript + notifyListeners("notification", data: eventData) + + completionHandler() + } + + // Handle notifications when app is in foreground + public func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { + var presentationOptions: UNNotificationPresentationOptions = [] + if #available(iOS 14.0, *) { + presentationOptions = [.banner, .sound, .badge] + } else { + presentationOptions = [.alert, .sound, .badge] + } + completionHandler(presentationOptions) } } \ No newline at end of file diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..71e99bd --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,19 @@ +platform :ios, '13.0' +use_frameworks! + +source 'https://cdn.cocoapods.org/' + +def capacitor_pods + pod 'Capacitor', :path => '../node_modules/@capacitor/ios' + pod 'CapacitorCordova', :path => '../node_modules/@capacitor/ios' +end + +target 'DailyNotificationPlugin' do + capacitor_pods + pod 'DailyNotificationPlugin', :path => '.' +end + +target 'DailyNotificationPluginTests' do + capacitor_pods + pod 'DailyNotificationPlugin', :path => '.' +end \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..e737c54 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,29 @@ +PODS: + - Capacitor (5.0.0): + - CapacitorCordova + - CapacitorCordova (5.0.0) + - DailyNotificationPlugin (1.0.0): + - Capacitor + - CapacitorCordova + +DEPENDENCIES: + - "Capacitor (from `../node_modules/@capacitor/ios`)" + - "CapacitorCordova (from `../node_modules/@capacitor/ios`)" + - DailyNotificationPlugin (from `.`) + +EXTERNAL SOURCES: + Capacitor: + :path: "../node_modules/@capacitor/ios" + CapacitorCordova: + :path: "../node_modules/@capacitor/ios" + DailyNotificationPlugin: + :path: "." + +SPEC CHECKSUMS: + Capacitor: ba8cd5cce13c6ab3c4faf7ef98487be481c9c1c8 + CapacitorCordova: 4ea17670ee562680988a7ce9db68dee5160fe564 + DailyNotificationPlugin: 59b7578061086ff48fd72151c36cdbd90f004bf5 + +PODFILE CHECKSUM: ac8c229d24347f6f83e67e6b95458e0b81e68f7c + +COCOAPODS: 1.16.2 diff --git a/ios/Tests/DailyNotificationTests.swift b/ios/Tests/DailyNotificationTests.swift new file mode 100644 index 0000000..d1c5bbe --- /dev/null +++ b/ios/Tests/DailyNotificationTests.swift @@ -0,0 +1,29 @@ +import XCTest +@testable import Plugin + +class DailyNotificationTests: XCTestCase { + var plugin: DailyNotificationPlugin! + + override func setUp() { + super.setUp() + plugin = DailyNotificationPlugin() + } + + func testTimeValidation() { + // Valid time + XCTAssertTrue(plugin.isValidTime("09:00")) + + // Invalid times + XCTAssertFalse(plugin.isValidTime("25:00")) + XCTAssertFalse(plugin.isValidTime("09:60")) + XCTAssertFalse(plugin.isValidTime("9:00")) + XCTAssertFalse(plugin.isValidTime("0900")) + } + + func testTimezoneValidation() { + XCTAssertTrue(plugin.isValidTimezone("America/New_York")) + XCTAssertFalse(plugin.isValidTimezone("Invalid/Timezone")) + } + + // Add more tests... +} \ No newline at end of file diff --git a/ios/project.yml b/ios/project.yml new file mode 100644 index 0000000..4c7e84b --- /dev/null +++ b/ios/project.yml @@ -0,0 +1,52 @@ +name: DailyNotificationPlugin +options: + bundleIdPrefix: com.timesafari + deploymentTarget: + iOS: 13.0 +schemes: + DailyNotificationPlugin: + build: + targets: + DailyNotificationPlugin: all + run: + config: Debug + test: + targets: + - DailyNotificationPluginTests + config: Debug + profile: + config: Release + analyze: + config: Debug + archive: + config: Release + DailyNotificationPluginTests: + build: + targets: + DailyNotificationPluginTests: all + test: + targets: + - DailyNotificationPluginTests + config: Debug +targets: + DailyNotificationPlugin: + type: framework + platform: iOS + sources: + - path: Plugin + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.timesafari.dailynotification + DEVELOPMENT_TEAM: "" # Add your team ID here + dependencies: [] + DailyNotificationPluginTests: + type: bundle.unit-test + platform: iOS + sources: + - path: Tests + dependencies: + - target: DailyNotificationPlugin + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.timesafari.dailynotification.tests + DEVELOPMENT_TEAM: "" # Add your team ID here \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bfa9bd9..ba7b97b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "license": "MIT", "dependencies": { "@capacitor/android": "^5.0.0", - "@capacitor/core": "^5.0.0" + "@capacitor/core": "^5.0.0", + "@capacitor/ios": "^5.0.0" }, "devDependencies": { "@capacitor/cli": "^5.0.0", @@ -702,6 +703,15 @@ "tslib": "^2.1.0" } }, + "node_modules/@capacitor/ios": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@capacitor/ios/-/ios-5.0.0.tgz", + "integrity": "sha512-b1edQNe1cKiqnxoIR5WxbVjDlf3RWr2ZjaDxwEuBzwBAvvrFcweKdbw1ij45DWHKODaIymWoyAlAUN+vFOF5sw==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": "^5.0.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", diff --git a/package.json b/package.json index 5a78b7f..d14aa44 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "license": "MIT", "dependencies": { "@capacitor/android": "^5.0.0", - "@capacitor/core": "^5.0.0" + "@capacitor/core": "^5.0.0", + "@capacitor/ios": "^5.0.0" }, "devDependencies": { "@capacitor/cli": "^5.0.0",