diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..4fdc0a3 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,19 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "env": { + "node": true, + "jest": true + }, + "rules": { + "@typescript-eslint/explicit-function-return-type": "warn", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 34d3813..b8ad22e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,61 @@ -dist/ +# Dependencies node_modules/ -.DS_Store -Pods/ -*.iml +npm-debug.log +yarn-debug.log +yarn-error.log + +# Build output +dist/ +build/ +*.tsbuildinfo + +# IDE .idea/ .vscode/ -build/ -*.tgz +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Android +android/app/build/ +android/build/ +android/gradle/ +android/gradlew +android/gradlew.bat +android/.gradle/ +android/local.properties +android/.idea/ +android/*.iml + +# iOS +ios/Pods/ +ios/build/ +ios/Podfile.lock +ios/.xcode.env.local +ios/DerivedData/ +ios/*.xcworkspace/ +ios/*.xcodeproj/* +!ios/*.xcodeproj/project.pbxproj +!ios/*.xcodeproj/xcshareddata/ +!ios/*.xcworkspace/contents.xcworkspacedata + +# Testing +coverage/ +.nyc_output/ + +# Logs +logs/ +*.log -# Ignore Gradle project-specific cache directory -.gradle +# Environment +.env +.env.local +.env.*.local -# Ignore Gradle build output directory -build +# Temporary files +*.tmp +*.temp +.cache/ diff --git a/.gradle/8.13/checksums/checksums.lock b/.gradle/8.13/checksums/checksums.lock new file mode 100644 index 0000000..949a81a Binary files /dev/null and b/.gradle/8.13/checksums/checksums.lock differ diff --git a/.gradle/8.13/checksums/md5-checksums.bin b/.gradle/8.13/checksums/md5-checksums.bin new file mode 100644 index 0000000..ca79566 Binary files /dev/null and b/.gradle/8.13/checksums/md5-checksums.bin differ diff --git a/.gradle/8.13/checksums/sha1-checksums.bin b/.gradle/8.13/checksums/sha1-checksums.bin new file mode 100644 index 0000000..47cfebf Binary files /dev/null and b/.gradle/8.13/checksums/sha1-checksums.bin differ diff --git a/.gradle/8.13/executionHistory/executionHistory.bin b/.gradle/8.13/executionHistory/executionHistory.bin new file mode 100644 index 0000000..94410c5 Binary files /dev/null and b/.gradle/8.13/executionHistory/executionHistory.bin differ diff --git a/.gradle/8.13/executionHistory/executionHistory.lock b/.gradle/8.13/executionHistory/executionHistory.lock new file mode 100644 index 0000000..947e9de Binary files /dev/null and b/.gradle/8.13/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.13/fileChanges/last-build.bin b/.gradle/8.13/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/.gradle/8.13/fileChanges/last-build.bin differ diff --git a/.gradle/8.13/fileHashes/fileHashes.bin b/.gradle/8.13/fileHashes/fileHashes.bin new file mode 100644 index 0000000..922f560 Binary files /dev/null and b/.gradle/8.13/fileHashes/fileHashes.bin differ diff --git a/.gradle/8.13/fileHashes/fileHashes.lock b/.gradle/8.13/fileHashes/fileHashes.lock new file mode 100644 index 0000000..fa8e103 Binary files /dev/null and b/.gradle/8.13/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.13/fileHashes/resourceHashesCache.bin b/.gradle/8.13/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000..de858ac Binary files /dev/null and b/.gradle/8.13/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/8.13/gc.properties b/.gradle/8.13/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..043502c Binary files /dev/null and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..9219816 --- /dev/null +++ b/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Fri Mar 28 09:01:29 UTC 2025 +gradle.version=8.13 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..088ea7b Binary files /dev/null and b/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/.globals.work.bin b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/.globals.work.bin new file mode 100644 index 0000000..56771ba Binary files /dev/null and b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/.globals.work.bin differ diff --git a/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/.strings.work.bin b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/.strings.work.bin new file mode 100644 index 0000000..81ddf99 Binary files /dev/null and b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/.strings.work.bin differ diff --git a/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/_.work.bin b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/_.work.bin new file mode 100644 index 0000000..a91fa75 Binary files /dev/null and b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/_.work.bin differ diff --git a/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/buildfingerprint.bin b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/buildfingerprint.bin new file mode 100644 index 0000000..fd359cd Binary files /dev/null and b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/buildfingerprint.bin differ diff --git a/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/entry.bin b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/entry.bin new file mode 100644 index 0000000..995b543 Binary files /dev/null and b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/entry.bin differ diff --git a/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/projectfingerprint.bin b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/projectfingerprint.bin new file mode 100644 index 0000000..3808dc2 Binary files /dev/null and b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/projectfingerprint.bin differ diff --git a/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/work.bin b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/work.bin new file mode 100644 index 0000000..7d6ba1e --- /dev/null +++ b/.gradle/configuration-cache/0809f6f5-185b-4807-b8df-47cb0fd70765/work.bin @@ -0,0 +1 @@ +_{=lLg͒wxD=HzQbK6E+Il/bzjB4`S8Oo}]y~u\tĞqTDVEqG5b*rD L-dC1'K%eA'p9]jPorzTb_OūxٙW6-Viy?ؐAB'z'P:Bc\Q*2[+ pTvmoٿAJyGؒP@fk`%w Wh\ӻn?D⏭ł˂ټDr]9bԂX^CRK UpYhΖ p*Rze @dVe_WKy=T$_Wfn!|R_ ɘ7 2w4|\:}H-y@2QKȐB@YT +yhGlse, +pe.Έ + + + + + + + ``` -## API Documentation +## Development -### Methods +### Prerequisites -#### scheduleDailyNotification(options: ScheduleOptions) -Schedule a new daily notification. +- Node.js 14 or later +- Android Studio +- Android SDK +- Gradle -#### getLastNotification() -Get information about the last delivered notification. +### Building -#### cancelAllNotifications() -Cancel all pending notifications. +```bash +# Install dependencies +npm install -#### getNotificationStatus() -Get current notification status and settings. +# Build the plugin +npm run build -#### updateSettings(settings: NotificationSettings) -Update notification settings. +# Run tests +npm test +``` -#### checkPermissions() -Check current notification permissions. +### Project Structure -#### requestPermissions() -Request notification permissions. +``` +daily-notification-plugin/ +├── android/ # Android implementation +│ ├── app/ # Main application module +│ └── build.gradle # Root build configuration +├── src/ # TypeScript source +├── tests/ # Test files +├── package.json # Package configuration +└── README.md # This file +``` -### Types +## Contributing -```typescript -interface ScheduleOptions { - url: string; - time: string; - title?: string; - body?: string; - sound?: boolean; - priority?: 'high' | 'default' | 'low'; - timezone?: string; -} - -interface NotificationSettings { - sound?: boolean; - priority?: string; - timezone?: string; -} -``` +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add some amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request -## Error Handling +## License -The plugin provides detailed error messages for various scenarios: -- Invalid parameters -- Permission issues -- Scheduling failures -- Invalid time formats -- Invalid timezone identifiers -- Invalid priority values +This project is licensed under the MIT License - see the LICENSE file for details. -## Contributing +## Author -Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository. +Matthew Raymer -## License +## Security + +This plugin follows security best practices: + +- Uses AndroidX for modern security features +- Implements proper permission handling +- Follows Android security guidelines +- Uses secure storage for sensitive data +- Implements proper error handling +- Logs security-relevant events +- Uses secure communication channels +- Implements proper access control +- Follows Android's security model +- Uses secure defaults + +## Changelog -MIT License - see LICENSE file for details +### 1.0.0 +- Initial release +- Basic notification scheduling +- System state handling +- Battery optimization support +- Background task management +- Rich logging system diff --git a/android/Configure b/android/Configure new file mode 100644 index 0000000..e69de29 diff --git a/android/app/build.gradle b/android/app/build.gradle index 0bb3814..43b3d51 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,15 +1,12 @@ apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'jacoco' android { namespace "com.timesafari.dailynotification" - compileSdkVersion 33 - buildToolsVersion "33.0.2" + compileSdkVersion rootProject.ext.compileSdkVersion defaultConfig { applicationId "com.timesafari.dailynotification" - minSdkVersion 22 - targetSdkVersion 33 + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -22,98 +19,27 @@ android { buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - lintOptions { - abortOnError false - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - testOptions { - unitTests.all { - useJUnitPlatform() + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } repositories { - google() - mavenCentral() - maven { - url "${project.rootDir}/capacitor-cordova-android-plugins/src/main/libs" - } - maven { - url "${project.rootDir}/libs" + flatDir{ + dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" + implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" + implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" implementation project(':capacitor-android') + testImplementation "junit:junit:$junitVersion" + androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" + androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" implementation project(':capacitor-cordova-android-plugins') - - // Android SDK - implementation 'com.android.support:support-v4:28.0.0' - implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.android.support:design:28.0.0' - - // AndroidX Core - implementation 'androidx.core:core:1.9.0' - implementation 'androidx.core:core-ktx:1.12.0' - - // AndroidX AppCompat - implementation 'androidx.appcompat:appcompat:1.6.1' - - // AndroidX Test - testImplementation 'androidx.test:core:1.5.0' - testImplementation 'androidx.test:runner:1.5.2' - testImplementation 'androidx.test.ext:junit:1.1.5' - testImplementation 'androidx.test.espresso:espresso-core:3.5.1' - testImplementation 'androidx.test:rules:1.5.0' - testImplementation 'androidx.test:core-ktx:1.5.0' - - // JUnit - testImplementation 'junit:junit:4.13.2' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' - testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.2' - - // Mockito - testImplementation 'org.mockito:mockito-core:4.5.1' - testImplementation 'org.mockito:mockito-inline:4.5.1' - - // AndroidX WorkManager - implementation 'androidx.work:work-runtime:2.8.1' - - // AndroidX Room (for local storage) - implementation 'androidx.room:room-runtime:2.6.1' - annotationProcessor 'androidx.room:room-compiler:2.6.1' - - // AndroidX Lifecycle - implementation 'androidx.lifecycle:lifecycle-runtime:2.7.0' - implementation 'androidx.lifecycle:lifecycle-common-java8:2.7.0' - - // AndroidX Security - implementation 'androidx.security:security-crypto:1.1.0-alpha06' - - // AndroidX Notification - implementation 'androidx.media:media:1.7.0' - - // AndroidX Broadcast - implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' - - // Capacitor dependencies - implementation 'com.getcapacitor:capacitor:5.0.0' - implementation 'com.getcapacitor:capacitor-android:5.0.0' - - // Testing dependencies - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - androidTestImplementation 'androidx.test:runner:1.5.2' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'org.mockito:mockito-android:4.5.1' } apply from: 'capacitor.build.gradle' @@ -124,5 +50,5 @@ try { apply plugin: 'com.google.gms.google-services' } } catch(Exception e) { - logger.warn("google-services.json not found, google-services plugin not applied. Push Notifications won't work") + logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") } diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index 42df55f..fdb4970 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -9,6 +9,11 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { - implementation project(':capacitor-android') - implementation project(':capacitor-cordova-android-plugins') -} \ No newline at end of file + + +} + + +if (hasProperty('postBuildExtras')) { + postBuildExtras() +} diff --git a/android/app/src/androidTest/java/com/timesafari/dailynotification/.LCKDailyNotificationPluginTest.java~ b/android/app/src/androidTest/java/com/timesafari/dailynotification/.LCKDailyNotificationPluginTest.java~ deleted file mode 100644 index 146077b..0000000 --- a/android/app/src/androidTest/java/com/timesafari/dailynotification/.LCKDailyNotificationPluginTest.java~ +++ /dev/null @@ -1 +0,0 @@ -/home/matthew/projects/timesafari/daily-notification-plugin/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationPluginTest.java \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationConfigTest.java b/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationConfigTest.java deleted file mode 100644 index bde1c97..0000000 --- a/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationConfigTest.java +++ /dev/null @@ -1,104 +0,0 @@ -/** - * DailyNotificationConfigTest.java - * Daily Notification Plugin for Capacitor - * - * Tests for the DailyNotificationConfig - * - * Features: - * - Unit tests - * - Singleton pattern - * - Configuration management - * - Default values - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - -import java.util.TimeZone; - -import static org.junit.Assert.*; - -@RunWith(MockitoJUnitRunner.class) -public class DailyNotificationConfigTest { - private DailyNotificationConfig config; - - @Before - public void setUp() { - config = DailyNotificationConfig.getInstance(); - } - - @Test - public void testSingletonPattern() { - DailyNotificationConfig config2 = DailyNotificationConfig.getInstance(); - assertSame("Config instances should be the same", config, config2); - } - - @Test - public void testDefaultValues() { - assertEquals("Default max notifications should be 10", 10, - config.getMaxNotificationsPerDay()); - assertEquals("Default timezone should be system default", - TimeZone.getDefault(), config.getDefaultTimeZone()); - assertTrue("Default logging should be enabled", config.isLoggingEnabled()); - assertEquals("Default retention days should be 7", 7, - config.getRetentionDays()); - } - - @Test - public void testMaxNotificationsPerDay() { - config.setMaxNotificationsPerDay(5); - assertEquals("Max notifications should be 5", 5, - config.getMaxNotificationsPerDay()); - } - - @Test - public void testDefaultTimeZone() { - TimeZone newTimeZone = TimeZone.getTimeZone("America/New_York"); - config.setDefaultTimeZone(newTimeZone); - assertEquals("Default timezone should be America/New_York", - newTimeZone, config.getDefaultTimeZone()); - } - - @Test - public void testLoggingEnabled() { - config.setLoggingEnabled(false); - assertFalse("Logging should be disabled", config.isLoggingEnabled()); - - config.setLoggingEnabled(true); - assertTrue("Logging should be enabled", config.isLoggingEnabled()); - } - - @Test - public void testRetentionDays() { - config.setRetentionDays(14); - assertEquals("Retention days should be 14", 14, - config.getRetentionDays()); - } - - @Test - public void testResetToDefaults() { - // Change values - config.setMaxNotificationsPerDay(5); - config.setDefaultTimeZone(TimeZone.getTimeZone("America/New_York")); - config.setLoggingEnabled(false); - config.setRetentionDays(14); - - // Reset to defaults - config.resetToDefaults(); - - // Verify defaults - assertEquals("Max notifications should be reset to 10", 10, - config.getMaxNotificationsPerDay()); - assertEquals("Default timezone should be reset to system default", - TimeZone.getDefault(), config.getDefaultTimeZone()); - assertTrue("Logging should be reset to enabled", config.isLoggingEnabled()); - assertEquals("Retention days should be reset to 7", 7, - config.getRetentionDays()); - } -} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationConstantsTest.java b/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationConstantsTest.java deleted file mode 100644 index 7d91baa..0000000 --- a/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationConstantsTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/** - * DailyNotificationConstantsTest.java - * Daily Notification Plugin for Capacitor - * - * Tests for the DailyNotificationConstants - * - * Features: - * - Unit tests - * - Constant validation - * - Default values - * - Error messages - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - -import static org.junit.Assert.*; - -@RunWith(MockitoJUnitRunner.class) -public class DailyNotificationConstantsTest { - - @Test - public void testDefaultValues() { - assertEquals("Default title should be 'Daily Notification'", - "Daily Notification", DailyNotificationConstants.DEFAULT_TITLE); - assertEquals("Default body should be 'Your daily update is ready'", - "Your daily update is ready", DailyNotificationConstants.DEFAULT_BODY); - } - - @Test - public void testNotificationIdentifiers() { - assertTrue("Notification ID prefix should start with 'daily-notification-'", - DailyNotificationConstants.NOTIFICATION_ID_PREFIX.startsWith("daily-notification-")); - assertEquals("Event name should be 'notification'", - "notification", DailyNotificationConstants.EVENT_NAME); - } - - @Test - public void testSettingsKeys() { - assertEquals("Sound setting key should be 'sound'", - "sound", DailyNotificationConstants.Settings.SOUND); - assertEquals("Priority setting key should be 'priority'", - "priority", DailyNotificationConstants.Settings.PRIORITY); - assertEquals("Timezone setting key should be 'timezone'", - "timezone", DailyNotificationConstants.Settings.TIMEZONE); - assertEquals("Retry count setting key should be 'retryCount'", - "retryCount", DailyNotificationConstants.Settings.RETRY_COUNT); - assertEquals("Retry interval setting key should be 'retryInterval'", - "retryInterval", DailyNotificationConstants.Settings.RETRY_INTERVAL); - } - - @Test - public void testSettingsDefaultValues() { - assertTrue("Default sound should be true", - DailyNotificationConstants.Settings.DEFAULT_SOUND); - assertEquals("Default priority should be 'default'", - "default", DailyNotificationConstants.Settings.DEFAULT_PRIORITY); - assertEquals("Default retry count should be 3", - 3, DailyNotificationConstants.Settings.DEFAULT_RETRY_COUNT); - assertEquals("Default retry interval should be 1000", - 1000, DailyNotificationConstants.Settings.DEFAULT_RETRY_INTERVAL); - } - - @Test - public void testPluginMethodKeys() { - assertEquals("URL key should be 'url'", - "url", DailyNotificationConstants.Keys.URL); - assertEquals("Time key should be 'time'", - "time", DailyNotificationConstants.Keys.TIME); - assertEquals("Title key should be 'title'", - "title", DailyNotificationConstants.Keys.TITLE); - assertEquals("Body key should be 'body'", - "body", DailyNotificationConstants.Keys.BODY); - assertEquals("Sound key should be 'sound'", - "sound", DailyNotificationConstants.Keys.SOUND); - assertEquals("Priority key should be 'priority'", - "priority", DailyNotificationConstants.Keys.PRIORITY); - assertEquals("Timezone key should be 'timezone'", - "timezone", DailyNotificationConstants.Keys.TIMEZONE); - } - - @Test - public void testChannelSettings() { - assertEquals("Channel ID should be 'daily_notification_channel'", - "daily_notification_channel", DailyNotificationConstants.Channel.ID); - assertEquals("Channel name should be 'Daily Notifications'", - "Daily Notifications", DailyNotificationConstants.Channel.NAME); - assertEquals("Channel description should be 'Daily notification updates'", - "Daily notification updates", DailyNotificationConstants.Channel.DESCRIPTION); - assertTrue("Channel should enable vibration", - DailyNotificationConstants.Channel.ENABLE_VIBRATION); - assertTrue("Channel should enable lights", - DailyNotificationConstants.Channel.ENABLE_LIGHTS); - } - - @Test - public void testTimeConstants() { - assertEquals("Milliseconds per day should be 86400000", - 86400000, DailyNotificationConstants.Time.MILLIS_PER_DAY); - assertEquals("Maximum hours should be 24", - 24, DailyNotificationConstants.Time.MAX_HOURS); - assertEquals("Maximum minutes should be 60", - 60, DailyNotificationConstants.Time.MAX_MINUTES); - } - - @Test - public void testErrorMessages() { - assertEquals("Missing parameters error message should be correct", - "Missing required parameters", DailyNotificationConstants.Errors.MISSING_PARAMS); - assertEquals("Invalid time error message should be correct", - "Invalid time format", DailyNotificationConstants.Errors.INVALID_TIME); - assertEquals("Invalid timezone error message should be correct", - "Invalid timezone", DailyNotificationConstants.Errors.INVALID_TIMEZONE); - assertEquals("Invalid priority error message should be correct", - "Invalid priority value", DailyNotificationConstants.Errors.INVALID_PRIORITY); - assertEquals("Scheduling failed error message should be correct", - "Failed to schedule notification", DailyNotificationConstants.Errors.SCHEDULING_FAILED); - assertEquals("Permission denied error message should be correct", - "Notification permission denied", DailyNotificationConstants.Errors.PERMISSION_DENIED); - } -} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationLoggerTest.java b/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationLoggerTest.java deleted file mode 100644 index b831caf..0000000 --- a/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationLoggerTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * DailyNotificationLoggerTest.java - * Daily Notification Plugin for Capacitor - * - * Tests for the DailyNotificationLogger - * - * Features: - * - Unit tests - * - Log level testing - * - Logging configuration - * - Error handling - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.junit.MockitoJUnitRunner; - -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -public class DailyNotificationLoggerTest { - private DailyNotificationLogger logger; - - @Before - public void setUp() { - logger = new DailyNotificationLogger(); - MockitoAnnotations.openMocks(this); - } - - @Test - public void testLogLevels() { - // Test each log level - logger.log("Debug message", DailyNotificationLogger.Level.DEBUG); - logger.log("Info message", DailyNotificationLogger.Level.INFO); - logger.log("Warning message", DailyNotificationLogger.Level.WARNING); - logger.log("Error message", DailyNotificationLogger.Level.ERROR); - - // Note: Actual verification would require capturing log output - } - - @Test - public void testLogWithThrowable() { - Exception e = new Exception("Test exception"); - logger.log("Error with exception", DailyNotificationLogger.Level.ERROR, e); - - // Note: Actual verification would require capturing log output - } - - @Test - public void testLoggingEnabled() { - // Test logging enabled - assertTrue(DailyNotificationLogger.isLoggingEnabled()); - - // Disable logging - DailyNotificationLogger.setLoggingEnabled(false); - assertFalse(DailyNotificationLogger.isLoggingEnabled()); - - // Re-enable logging - DailyNotificationLogger.setLoggingEnabled(true); - assertTrue(DailyNotificationLogger.isLoggingEnabled()); - } - - @Test - public void testLogMessageFormat() { - String message = "Test message"; - logger.log(message, DailyNotificationLogger.Level.INFO); - - // Note: Actual verification would require capturing log output - } - - @Test - public void testNullMessage() { - logger.log(null, DailyNotificationLogger.Level.INFO); - - // Note: Actual verification would require capturing log output - } -} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationPluginTest.java b/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationPluginTest.java deleted file mode 100644 index 98ee38c..0000000 --- a/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationPluginTest.java +++ /dev/null @@ -1,227 +0,0 @@ -/** - * DailyNotificationPluginTest.java - * Daily Notification Plugin for Capacitor - * - * Tests for the DailyNotification plugin - * - * Features: - * - Unit tests - * - Integration tests - * - Edge cases - * - Error scenarios - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Build; -import android.os.PowerManager; -import android.os.BatteryManager; - -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - -import com.getcapacitor.JSObject; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginMethod; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -@RunWith(AndroidJUnit4.class) -public class DailyNotificationPluginTest { - private DailyNotificationPlugin plugin; - private Context context; - private SharedPreferences settings; - - @Mock - private PluginCall mockCall; - - @Before - public void setUp() { - context = ApplicationProvider.getApplicationContext(); - settings = context.getSharedPreferences("test_settings", Context.MODE_PRIVATE); - plugin = new DailyNotificationPlugin(); - MockitoAnnotations.openMocks(this); - } - - @Test - public void testTimeValidation() { - // Valid time - assertTrue(plugin.isValidTime("09:00")); - assertTrue(plugin.isValidTime("00:00")); - assertTrue(plugin.isValidTime("23:59")); - - // Invalid times - assertFalse(plugin.isValidTime("24:00")); - assertFalse(plugin.isValidTime("12:60")); - assertFalse(plugin.isValidTime("9:00")); - assertFalse(plugin.isValidTime("13:5")); - } - - @Test - public void testTimezoneValidation() { - assertTrue(plugin.isValidTimezone("America/New_York")); - assertTrue(plugin.isValidTimezone("Europe/London")); - assertTrue(plugin.isValidTimezone("Asia/Kolkata")); - - assertFalse(plugin.isValidTimezone("Invalid/Timezone")); - assertFalse(plugin.isValidTimezone("America/Invalid")); - } - - @Test - public void testNotificationScheduling() { - when(mockCall.getString("url")).thenReturn("https://example.com"); - when(mockCall.getString("time")).thenReturn("09:00"); - when(mockCall.getString("title")).thenReturn("Test Notification"); - when(mockCall.getString("body")).thenReturn("Test Body"); - - plugin.scheduleDailyNotification(mockCall); - - // Verify notification was scheduled - // Note: Actual verification would require mocking AlarmManager - } - - @Test - public void testSettingsManagement() { - when(mockCall.hasOption("sound")).thenReturn(true); - when(mockCall.getBoolean("sound")).thenReturn(false); - when(mockCall.hasOption("priority")).thenReturn(true); - when(mockCall.getString("priority")).thenReturn("high"); - - plugin.updateSettings(mockCall); - - // Verify settings were updated - assertEquals(false, settings.getBoolean("sound", true)); - assertEquals("high", settings.getString("priority", "default")); - } - - @Test - public void testNotificationCancellation() { - plugin.cancelAllNotifications(mockCall); - - // Verify notifications were cancelled - // Note: Actual verification would require mocking NotificationManager - } - - @Test - public void testNotificationStatus() { - plugin.getNotificationStatus(mockCall); - - // Verify status was retrieved - // Note: Actual verification would require mocking NotificationManager - } - - @Test - public void testErrorHandling() { - when(mockCall.getString("url")).thenReturn(null); - when(mockCall.getString("time")).thenReturn(null); - - plugin.scheduleDailyNotification(mockCall); - - // Verify error was handled - verify(mockCall).reject(contains("Missing required parameters")); - } - - @Test - public void testBackgroundTaskHandling() { - // Test background task scheduling - // Note: Actual verification would require mocking WorkManager - } - - @Test - public void testBatteryOptimization() { - // Mock PowerManager - PowerManager mockPowerManager = mock(PowerManager.class); - when(context.getSystemService(Context.POWER_SERVICE)).thenReturn(mockPowerManager); - - // Test battery optimization exemption check - when(mockPowerManager.isIgnoringBatteryOptimizations(anyString())).thenReturn(true); - plugin.checkBatteryOptimization(); - - // Test Doze mode detection - when(mockPowerManager.isDeviceIdleMode()).thenReturn(true); - assertTrue(plugin.isDeviceInDozeMode()); - - // Test wake lock acquisition and release - PowerManager.WakeLock mockWakeLock = mock(PowerManager.WakeLock.class); - when(mockPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mockWakeLock); - when(mockWakeLock.isHeld()).thenReturn(false); - - plugin.acquireWakeLock(); - verify(mockWakeLock).acquire(anyLong()); - - when(mockWakeLock.isHeld()).thenReturn(true); - plugin.releaseWakeLock(); - verify(mockWakeLock).release(); - - // Test battery optimization exemption request - PluginCall mockCall = mock(PluginCall.class); - plugin.requestBatteryOptimizationExemption(mockCall); - verify(context).startActivity(any(Intent.class)); - } - - @Test - public void testNotificationChannelCreation() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - // Verify notification channel was created - // Note: Actual verification would require mocking NotificationManager - } - } - - @Test - public void testRetryLogic() { - // Test retry mechanism - // Note: Actual verification would require mocking network calls - } - - @Test - public void testEventHandling() { - // Test notification event handling - // Note: Actual verification would require mocking event system - } - - @Test - public void testResourceCleanup() { - // Test resource cleanup - // Note: Actual verification would require monitoring system resources - } - - @Test - public void testBatteryMonitoring() { - // Mock battery status intent - Intent batteryStatus = new Intent(Intent.ACTION_BATTERY_CHANGED); - batteryStatus.putExtra(BatteryManager.EXTRA_LEVEL, 75); - batteryStatus.putExtra(BatteryManager.EXTRA_SCALE, 100); - batteryStatus.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_HEALTH_CHARGING); - - // Send battery status broadcast - context.sendBroadcast(batteryStatus); - - // Verify battery status was updated - assertEquals(75, plugin.getBatteryLevel()); - assertTrue(plugin.isCharging()); - - // Test battery status retrieval - PluginCall mockCall = mock(PluginCall.class); - plugin.getBatteryStatus(mockCall); - verify(mockCall).resolve(any(JSObject.class)); - - // Verify battery stats were stored - SharedPreferences prefs = context.getSharedPreferences(SETTINGS_KEY, Context.MODE_PRIVATE); - assertEquals(75, prefs.getInt("last_battery_level", -1)); - assertTrue(prefs.getBoolean("last_charging_state", false)); - assertTrue(prefs.getLong("last_battery_check", 0) > 0); - } -} \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationReceiverTest.java b/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationReceiverTest.java deleted file mode 100644 index 0fe1193..0000000 --- a/android/app/src/androidTest/java/com/timesafari/dailynotification/DailyNotificationReceiverTest.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * DailyNotificationReceiverTest.java - * Daily Notification Plugin for Capacitor - * - * Tests for the DailyNotificationReceiver - * - * Features: - * - Unit tests - * - Integration tests - * - Edge cases - * - Error scenarios - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; - -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -@RunWith(AndroidJUnit4.class) -public class DailyNotificationReceiverTest { - private DailyNotificationReceiver receiver; - private Context context; - - @Before - public void setUp() { - context = ApplicationProvider.getApplicationContext(); - receiver = new DailyNotificationReceiver(); - } - - @Test - public void testValidNotificationData() { - Intent intent = new Intent(); - intent.putExtra("url", "https://example.com"); - intent.putExtra("title", "Test Notification"); - intent.putExtra("body", "Test Body"); - intent.putExtra("sound", true); - intent.putExtra("priority", "high"); - - receiver.onReceive(context, intent); - - // Note: Actual verification would require mocking NotificationManager - } - - @Test - public void testMissingNotificationData() { - Intent intent = new Intent(); - intent.putExtra("url", "https://example.com"); - // Missing title and body - - receiver.onReceive(context, intent); - - // Note: Actual verification would require checking logs - } - - @Test - public void testInvalidPriority() { - Intent intent = new Intent(); - intent.putExtra("url", "https://example.com"); - intent.putExtra("title", "Test Notification"); - intent.putExtra("body", "Test Body"); - intent.putExtra("priority", "invalid"); - - receiver.onReceive(context, intent); - - // Note: Actual verification would require checking logs - } - - @Test - public void testNotificationTap() { - Intent intent = new Intent(); - intent.putExtra("url", "https://example.com"); - intent.putExtra("title", "Test Notification"); - intent.putExtra("body", "Test Body"); - - // Note: Actual verification would require mocking PendingIntent - } - - @Test - public void testSoundSettings() { - Intent intent = new Intent(); - intent.putExtra("url", "https://example.com"); - intent.putExtra("title", "Test Notification"); - intent.putExtra("body", "Test Body"); - intent.putExtra("sound", false); - - receiver.onReceive(context, intent); - - // Note: Actual verification would require mocking NotificationManager - } -} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 13ff14c..4d7ca38 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,21 +1,5 @@ - - - - - - - - - - - - - - - + + android:theme="@style/AppTheme"> + android:exported="true"> @@ -50,4 +34,8 @@ android:resource="@xml/file_paths"> + + + + diff --git a/android/app/src/main/java/README.md b/android/app/src/main/java/README.md deleted file mode 100644 index 29e89d5..0000000 --- a/android/app/src/main/java/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Android Implementation - -This directory contains the Android-specific implementation of the DailyNotification plugin. - -## Implementation Details - -The Android implementation uses: - -- `WorkManager` for periodic data fetching -- `AlarmManager` for precise notification scheduling -- `SharedPreferences` for local data storage -- Android's notification channels for proper notification display - -## Native Code Location - -The native Android implementation is located in the `android/` directory at the project root. - -## Key Components - -1. `DailyNotificationAndroid.java`: Main plugin class -2. `FetchWorker.java`: Background work for data fetching -3. `NotificationReceiver.java`: Handles notification display -4. `NotificationHelper.java`: Manages notification creation and display - -## Implementation Notes - -- Uses Android's WorkManager for reliable background tasks -- Implements proper battery optimization handling -- Supports Android 8.0+ notification channels -- Handles Doze mode and battery optimizations -- Uses SharedPreferences for lightweight data storage - -## Testing - -Run Android-specific tests with: - -```bash -npm run test:android -``` \ No newline at end of file diff --git a/android/app/src/main/java/com/timesafari/dailynotification/BatteryOptimizationSettings.java b/android/app/src/main/java/com/timesafari/dailynotification/BatteryOptimizationSettings.java deleted file mode 100644 index 41c9afc..0000000 --- a/android/app/src/main/java/com/timesafari/dailynotification/BatteryOptimizationSettings.java +++ /dev/null @@ -1,225 +0,0 @@ -/** - * BatteryOptimizationSettings.java - * Daily Notification Plugin for Capacitor - * - * Manages battery optimization settings and power state monitoring - * - * Features: - * - Battery optimization exemption management - * - Power state monitoring - * - Adaptive scheduling based on power state - * - Battery level monitoring - * - Doze mode handling - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.BatteryManager; -import android.os.Build; -import android.os.PowerManager; -import android.provider.Settings; -import android.content.SharedPreferences; - -import androidx.work.Constraints; -import androidx.work.OneTimeWorkRequest; -import androidx.work.WorkManager; - -import java.util.concurrent.TimeUnit; - -public class BatteryOptimizationSettings { - private static final String TAG = "BatteryOptimizationSettings"; - private static final String BATTERY_OPTIMIZATION_KEY = "battery_optimization_exempt"; - private static final String POWER_STATE_KEY = "power_state"; - private static final String BATTERY_CHECK_INTERVAL = "battery_check_interval"; - private static final long DEFAULT_CHECK_INTERVAL = TimeUnit.HOURS.toMillis(1); - - private final Context context; - private final PowerManager powerManager; - private final SharedPreferences settings; - private final DailyNotificationLogger logger; - - private boolean isOptimizationExempt = false; - private int powerState = PowerManager.POWER_STATE_NORMAL; - private int batteryLevel = 0; - private boolean isCharging = false; - private long lastBatteryCheck = 0; - - public BatteryOptimizationSettings(Context context, SharedPreferences settings) { - this.context = context; - this.settings = settings; - this.powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - this.logger = new DailyNotificationLogger(); - - // Load saved state - isOptimizationExempt = settings.getBoolean(BATTERY_OPTIMIZATION_KEY, false); - powerState = settings.getInt(POWER_STATE_KEY, PowerManager.POWER_STATE_NORMAL); - lastBatteryCheck = settings.getLong(BATTERY_CHECK_INTERVAL, 0); - } - - public boolean requestBatteryOptimizationExemption() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - try { - // Check if already exempt - if (isOptimizationExempt) { - logger.log("App already exempt from battery optimization", - DailyNotificationLogger.Level.INFO); - return true; - } - - // Create intent for battery optimization settings - Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(Uri.parse("package:" + context.getPackageName())); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - // Store request time - long requestTime = System.currentTimeMillis(); - SharedPreferences.Editor editor = settings.edit(); - editor.putLong("last_optimization_request", requestTime); - editor.apply(); - - // Start settings activity - context.startActivity(intent); - - // Schedule a check for the optimization status - scheduleOptimizationCheck(); - - logger.log("Battery optimization exemption requested", - DailyNotificationLogger.Level.INFO); - return true; - } catch (Exception e) { - logger.log("Error requesting battery optimization exemption: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - return false; - } - } - return false; - } - - private void scheduleOptimizationCheck() { - OneTimeWorkRequest checkWork = new OneTimeWorkRequest.Builder(BatteryCheckWorker.class) - .setInitialDelay(5, TimeUnit.SECONDS) - .setConstraints(new Constraints.Builder() - .setRequiresBatteryNotLow(true) - .build()) - .build(); - - WorkManager.getInstance(context).enqueue(checkWork); - } - - public void updatePowerState() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - try { - int newPowerState = powerManager.getPowerState(); - if (newPowerState != powerState) { - powerState = newPowerState; - SharedPreferences.Editor editor = settings.edit(); - editor.putInt(POWER_STATE_KEY, powerState); - editor.apply(); - - logger.log("Power state changed to: " + getPowerStateString(powerState), - DailyNotificationLogger.Level.INFO); - - adjustSchedulingForPowerState(); - } - } catch (Exception e) { - logger.log("Error updating power state: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - } - } - } - - public void updateBatteryStatus() { - try { - Intent batteryStatus = context.registerReceiver(null, - new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - - if (batteryStatus != null) { - int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); - float batteryPct = level * 100 / (float)scale; - - int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); - boolean charging = status == BatteryManager.BATTERY_HEALTH_CHARGING || - status == BatteryManager.BATTERY_HEALTH_FULL; - - batteryLevel = (int)batteryPct; - isCharging = charging; - lastBatteryCheck = System.currentTimeMillis(); - - // Store battery stats - SharedPreferences.Editor editor = settings.edit(); - editor.putInt("last_battery_level", batteryLevel); - editor.putBoolean("last_charging_state", isCharging); - editor.putLong("last_battery_check", lastBatteryCheck); - editor.apply(); - - logger.log(String.format("Battery status updated: %d%% (%s)", - batteryLevel, - isCharging ? "charging" : "not charging"), - DailyNotificationLogger.Level.DEBUG); - } - } catch (Exception e) { - logger.log("Error updating battery status: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - } - } - - private void adjustSchedulingForPowerState() { - switch (powerState) { - case PowerManager.POWER_STATE_SAVE: - // In power save mode, increase the interval between checks - lastBatteryCheck = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2); - break; - case PowerManager.POWER_STATE_NORMAL: - // In normal mode, use standard intervals - lastBatteryCheck = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(30); - break; - case PowerManager.POWER_STATE_OPTIMIZED: - // In optimized mode, use longer intervals - lastBatteryCheck = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(4); - break; - } - } - - private String getPowerStateString(int state) { - switch (state) { - case PowerManager.POWER_STATE_SAVE: - return "POWER_SAVE"; - case PowerManager.POWER_STATE_NORMAL: - return "NORMAL"; - case PowerManager.POWER_STATE_OPTIMIZED: - return "OPTIMIZED"; - default: - return "UNKNOWN"; - } - } - - public boolean isOptimizationExempt() { - return isOptimizationExempt; - } - - public int getPowerState() { - return powerState; - } - - public int getBatteryLevel() { - return batteryLevel; - } - - public boolean isCharging() { - return isCharging; - } - - public long getLastBatteryCheck() { - return lastBatteryCheck; - } - - public boolean shouldCheckBattery() { - return System.currentTimeMillis() - lastBatteryCheck > DEFAULT_CHECK_INTERVAL; - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationConfig.java b/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationConfig.java deleted file mode 100644 index c881f90..0000000 --- a/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationConfig.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * DailyNotificationConfig.java - * Daily Notification Plugin for Capacitor - * - * Configuration manager for the Daily Notification plugin - * - * Features: - * - Singleton pattern - * - Configuration options - * - Default values - * - Settings persistence - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import java.util.TimeZone; - -public class DailyNotificationConfig { - private static DailyNotificationConfig instance; - - private int maxNotificationsPerDay; - private TimeZone defaultTimeZone; - private boolean loggingEnabled; - private int retentionDays; - - private DailyNotificationConfig() { - resetToDefaults(); - } - - public static synchronized DailyNotificationConfig getInstance() { - if (instance == null) { - instance = new DailyNotificationConfig(); - } - return instance; - } - - public void resetToDefaults() { - maxNotificationsPerDay = 10; - defaultTimeZone = TimeZone.getDefault(); - loggingEnabled = true; - retentionDays = 7; - } - - public int getMaxNotificationsPerDay() { - return maxNotificationsPerDay; - } - - public void setMaxNotificationsPerDay(int maxNotificationsPerDay) { - if (maxNotificationsPerDay < 1) { - throw new IllegalArgumentException("Max notifications per day must be greater than 0"); - } - this.maxNotificationsPerDay = maxNotificationsPerDay; - } - - public TimeZone getDefaultTimeZone() { - return defaultTimeZone; - } - - public void setDefaultTimeZone(TimeZone defaultTimeZone) { - if (defaultTimeZone == null) { - throw new IllegalArgumentException("Default timezone cannot be null"); - } - this.defaultTimeZone = defaultTimeZone; - } - - public boolean isLoggingEnabled() { - return loggingEnabled; - } - - public void setLoggingEnabled(boolean loggingEnabled) { - this.loggingEnabled = loggingEnabled; - DailyNotificationLogger.setLoggingEnabled(loggingEnabled); - } - - public int getRetentionDays() { - return retentionDays; - } - - public void setRetentionDays(int retentionDays) { - if (retentionDays < 1) { - throw new IllegalArgumentException("Retention days must be greater than 0"); - } - this.retentionDays = retentionDays; - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationConstants.java b/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationConstants.java deleted file mode 100644 index 1389ef9..0000000 --- a/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationConstants.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * DailyNotificationConstants.java - * Daily Notification Plugin for Capacitor - * - * Constants used throughout the Daily Notification plugin - * - * Features: - * - Default values - * - Notification identifiers - * - Settings keys - * - Channel settings - * - Time constants - * - Error messages - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -public class DailyNotificationConstants { - // Default values - public static final String DEFAULT_TITLE = "Daily Notification"; - public static final String DEFAULT_BODY = "Your daily update is ready"; - - // Notification identifiers - public static final String NOTIFICATION_ID_PREFIX = "daily-notification-"; - public static final String EVENT_NAME = "notification"; - - // Settings class - public static class Settings { - public static final String SOUND = "sound"; - public static final String PRIORITY = "priority"; - public static final String TIMEZONE = "timezone"; - public static final String RETRY_COUNT = "retryCount"; - public static final String RETRY_INTERVAL = "retryInterval"; - - public static final boolean DEFAULT_SOUND = true; - public static final String DEFAULT_PRIORITY = "default"; - public static final int DEFAULT_RETRY_COUNT = 3; - public static final int DEFAULT_RETRY_INTERVAL = 1000; - } - - // Plugin method keys - public static class Keys { - public static final String URL = "url"; - public static final String TIME = "time"; - public static final String TITLE = "title"; - public static final String BODY = "body"; - public static final String SOUND = "sound"; - public static final String PRIORITY = "priority"; - public static final String TIMEZONE = "timezone"; - } - - // Channel settings - public static class Channel { - public static final String ID = "daily_notification_channel"; - public static final String NAME = "Daily Notifications"; - public static final String DESCRIPTION = "Daily notification updates"; - public static final boolean ENABLE_VIBRATION = true; - public static final boolean ENABLE_LIGHTS = true; - } - - // Time constants - public static class Time { - public static final long MILLIS_PER_DAY = 86400000; - public static final int MAX_HOURS = 24; - public static final int MAX_MINUTES = 60; - } - - // Error messages - public static class Errors { - public static final String MISSING_PARAMS = "Missing required parameters"; - public static final String INVALID_TIME = "Invalid time format"; - public static final String INVALID_TIMEZONE = "Invalid timezone"; - public static final String INVALID_PRIORITY = "Invalid priority value"; - public static final String SCHEDULING_FAILED = "Failed to schedule notification"; - public static final String PERMISSION_DENIED = "Notification permission denied"; - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationLogger.java b/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationLogger.java deleted file mode 100644 index 76d7bcd..0000000 --- a/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationLogger.java +++ /dev/null @@ -1,92 +0,0 @@ -/** - * DailyNotificationLogger.java - * Daily Notification Plugin for Capacitor - * - * Logging utility for the Daily Notification plugin - * - * Features: - * - Log levels (DEBUG, INFO, WARNING, ERROR) - * - Throwable support - * - Enable/disable logging - * - Tag-based logging - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import android.util.Log; - -public class DailyNotificationLogger { - private static final String TAG = "DailyNotification"; - private static boolean loggingEnabled = true; - - public enum Level { - DEBUG, - INFO, - WARNING, - ERROR - } - - public static void setLoggingEnabled(boolean enabled) { - loggingEnabled = enabled; - } - - public static boolean isLoggingEnabled() { - return loggingEnabled; - } - - public void log(String message, Level level) { - log(message, level, null); - } - - public void log(String message, Level level, Throwable throwable) { - if (!loggingEnabled) { - return; - } - - String formattedMessage = formatMessage(message); - - switch (level) { - case DEBUG: - if (throwable != null) { - Log.d(TAG, formattedMessage, throwable); - } else { - Log.d(TAG, formattedMessage); - } - break; - - case INFO: - if (throwable != null) { - Log.i(TAG, formattedMessage, throwable); - } else { - Log.i(TAG, formattedMessage); - } - break; - - case WARNING: - if (throwable != null) { - Log.w(TAG, formattedMessage, throwable); - } else { - Log.w(TAG, formattedMessage); - } - break; - - case ERROR: - if (throwable != null) { - Log.e(TAG, formattedMessage, throwable); - } else { - Log.e(TAG, formattedMessage); - } - break; - } - } - - private String formatMessage(String message) { - if (message == null) { - return "null"; - } - - return String.format("[%s] %s", TAG, message); - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java b/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java deleted file mode 100644 index 7bd6bca..0000000 --- a/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java +++ /dev/null @@ -1,736 +0,0 @@ -/** - * DailyNotificationPlugin.java - * Daily Notification Plugin for Capacitor - * - * Handles daily notification scheduling and management on Android - * - * Features: - * - Daily notification scheduling with precise timing - * - Notification channel management - * - Settings persistence - * - Background task handling - * - Battery optimization support - * - Error handling and logging - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import android.app.AlarmManager; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.os.BatteryManager; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.PowerManager; -import android.os.SystemClock; -import android.provider.Settings; -import android.util.Log; - -import com.getcapacitor.JSObject; -import com.getcapacitor.Plugin; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginMethod; -import com.getcapacitor.annotation.CapacitorPlugin; - -import java.util.Calendar; -import java.util.TimeZone; -import java.util.concurrent.TimeUnit; - -import androidx.work.Constraints; -import androidx.work.OneTimeWorkRequest; -import androidx.work.WorkManager; - -@CapacitorPlugin(name = "DailyNotification") -public class DailyNotificationPlugin extends Plugin { - private static final String TAG = "DailyNotificationPlugin"; - private static final String CHANNEL_ID = "daily_notification_channel"; - private static final String CHANNEL_NAME = "Daily Notifications"; - private static final String CHANNEL_DESCRIPTION = "Daily notification updates"; - private static final String SETTINGS_KEY = "daily_notification_settings"; - private static final String BATTERY_STATS_KEY = "battery_stats"; - private static final String BATTERY_OPTIMIZATION_KEY = "battery_optimization_status"; - private static final String BATTERY_CHECK_INTERVAL = "battery_check_interval"; - private static final String LAST_NOTIFICATION_KEY = "last_notification_time"; - private static final String POWER_STATE_KEY = "power_state"; - private static final String ADAPTIVE_SCHEDULING_KEY = "adaptive_scheduling"; - - private NotificationManager notificationManager; - private AlarmManager alarmManager; - private PowerManager powerManager; - private PowerManager.WakeLock wakeLock; - private Context context; - private SharedPreferences settings; - private DailyNotificationLogger logger; - private boolean isOptimizationExempt = false; - private long lastBatteryCheck; - private int batteryLevel; - private boolean isCharging; - private long lastOptimizationCheck; - private boolean adaptiveSchedulingEnabled = true; - private int powerState = PowerManager.POWER_STATE_NORMAL; - - @Override - public void load() { - context = getContext(); - notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - settings = context.getSharedPreferences(SETTINGS_KEY, Context.MODE_PRIVATE); - logger = new DailyNotificationLogger(); - - createNotificationChannel(); - initializeSettings(); - checkBatteryOptimization(); - initializeBatteryMonitoring(); - logger.log("Plugin loaded successfully", DailyNotificationLogger.Level.INFO); - } - - private void initializeSettings() { - SharedPreferences.Editor editor = settings.edit(); - if (!settings.contains("sound")) editor.putBoolean("sound", true); - if (!settings.contains("priority")) editor.putString("priority", "default"); - if (!settings.contains("retryCount")) editor.putInt("retryCount", 3); - if (!settings.contains("retryInterval")) editor.putInt("retryInterval", 1000); - editor.apply(); - } - - private void createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channel = new NotificationChannel( - CHANNEL_ID, - CHANNEL_NAME, - NotificationManager.IMPORTANCE_DEFAULT - ); - channel.setDescription(CHANNEL_DESCRIPTION); - channel.enableVibration(true); - channel.enableLights(true); - notificationManager.createNotificationChannel(channel); - logger.log("Notification channel created", DailyNotificationLogger.Level.INFO); - } - } - - private void checkBatteryOptimization() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - String packageName = context.getPackageName(); - isOptimizationExempt = powerManager.isIgnoringBatteryOptimizations(packageName); - lastOptimizationCheck = System.currentTimeMillis(); - - // Store optimization status - SharedPreferences.Editor editor = settings.edit(); - editor.putBoolean(BATTERY_OPTIMIZATION_KEY, isOptimizationExempt); - editor.putLong("last_optimization_check", lastOptimizationCheck); - editor.apply(); - - logger.log("Battery optimization status: " + (isOptimizationExempt ? "exempt" : "not exempt"), - DailyNotificationLogger.Level.INFO); - - // If not exempt and haven't checked recently, request exemption - if (!isOptimizationExempt && - System.currentTimeMillis() - lastOptimizationCheck > BATTERY_CHECK_INTERVAL) { - requestBatteryOptimizationExemption(null); - } - } - } - - @PluginMethod - public void requestBatteryOptimizationExemption(PluginCall call) { - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - // Check if we already have exemption - if (isOptimizationExempt) { - logger.log("App already exempt from battery optimization", - DailyNotificationLogger.Level.INFO); - if (call != null) { - call.resolve(); - } - return; - } - - // Create intent for battery optimization settings - Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(android.net.Uri.parse("package:" + context.getPackageName())); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - // Store current time for tracking - long requestTime = System.currentTimeMillis(); - SharedPreferences.Editor editor = settings.edit(); - editor.putLong("last_optimization_request", requestTime); - editor.apply(); - - // Start the activity - context.startActivity(intent); - - // Schedule a check for the optimization status after a delay - new Handler(Looper.getMainLooper()).postDelayed(() -> { - checkBatteryOptimization(); - if (call != null) { - JSObject result = new JSObject(); - result.put("isExempt", isOptimizationExempt); - result.put("requestTime", requestTime); - result.put("checkTime", System.currentTimeMillis()); - call.resolve(result); - } - }, 5000); // Check after 5 seconds - - logger.log("Battery optimization exemption requested", - DailyNotificationLogger.Level.INFO); - } else { - if (call != null) { - call.reject("Battery optimization exemption not supported on this Android version"); - } - } - } catch (Exception e) { - logger.log("Error requesting battery optimization exemption: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - if (call != null) { - call.reject("Failed to request battery optimization exemption: " + e.getMessage()); - } - } - } - - private void acquireWakeLock() { - if (wakeLock == null) { - wakeLock = powerManager.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, - "DailyNotification:WakeLock" - ); - } - - if (!wakeLock.isHeld()) { - // Adjust wake lock duration based on battery level - long duration = TimeUnit.MINUTES.toMillis(1); - if (batteryLevel < 15) { - duration = TimeUnit.SECONDS.toMillis(30); - } else if (batteryLevel < 30) { - duration = TimeUnit.SECONDS.toMillis(45); - } - - wakeLock.acquire(duration); - logger.log("Wake lock acquired for " + duration + "ms", DailyNotificationLogger.Level.DEBUG); - } - } - - private void releaseWakeLock() { - if (wakeLock != null && wakeLock.isHeld()) { - wakeLock.release(); - logger.log("Wake lock released", DailyNotificationLogger.Level.DEBUG); - } - } - - private boolean isDeviceInDozeMode() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - return powerManager.isDeviceIdleMode(); - } - return false; - } - - private void handleDozeMode() { - if (isDeviceInDozeMode()) { - logger.log("Device is in Doze mode", DailyNotificationLogger.Level.WARNING); - - // If not exempt from battery optimization, try to request exemption - if (!isOptimizationExempt) { - requestBatteryOptimizationExemption(null); - } - - // Schedule a maintenance window for the next time the device exits Doze mode - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - // Use WorkManager for more reliable scheduling during Doze - OneTimeWorkRequest maintenanceWork = new OneTimeWorkRequest.Builder(MaintenanceWorker.class) - .setInitialDelay(15, TimeUnit.MINUTES) - .setConstraints(new Constraints.Builder() - .setRequiresDeviceIdle(false) - .setRequiresBatteryNotLow(true) - .build()) - .build(); - - WorkManager.getInstance(context).enqueue(maintenanceWork); - - // Also set an alarm as backup - alarmManager.setAndAllowWhileIdle( - AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(15), - createMaintenancePendingIntent() - ); - } - } - } - - private PendingIntent createMaintenancePendingIntent() { - Intent intent = new Intent(context, DailyNotificationReceiver.class); - intent.setAction("MAINTENANCE_WINDOW"); - return PendingIntent.getBroadcast( - context, - 0, - intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); - } - - @PluginMethod - public void scheduleDailyNotification(PluginCall call) { - try { - // Check battery optimization status - if (!isOptimizationExempt) { - logger.log("Warning: App is not exempt from battery optimization", - DailyNotificationLogger.Level.WARNING); - } - - // Check Doze mode - handleDozeMode(); - - // Acquire wake lock for scheduling - acquireWakeLock(); - - String url = call.getString("url"); - String time = call.getString("time"); - - if (url == null || time == null) { - throw new IllegalArgumentException("Missing required parameters"); - } - - // Parse time string (HH:mm format) - String[] timeComponents = time.split(":"); - if (timeComponents.length != 2) { - throw new IllegalArgumentException("Invalid time format"); - } - - int hour = Integer.parseInt(timeComponents[0]); - int minute = Integer.parseInt(timeComponents[1]); - - if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60) { - throw new IllegalArgumentException("Invalid time values"); - } - - // Create calendar instance for the specified time - Calendar calendar = Calendar.getInstance(); - calendar.set(Calendar.HOUR_OF_DAY, hour); - calendar.set(Calendar.MINUTE, minute); - calendar.set(Calendar.SECOND, 0); - - // If the time has already passed today, schedule for tomorrow - if (calendar.getTimeInMillis() <= System.currentTimeMillis()) { - calendar.add(Calendar.DAY_OF_YEAR, 1); - } - - // Create intent for the notification - Intent intent = new Intent(context, DailyNotificationReceiver.class); - intent.putExtra("url", url); - intent.putExtra("title", call.getString("title", "Daily Notification")); - intent.putExtra("body", call.getString("body", "Your daily update is ready")); - intent.putExtra("sound", call.getBoolean("sound", settings.getBoolean("sound", true))); - intent.putExtra("priority", call.getString("priority", settings.getString("priority", "default"))); - - PendingIntent pendingIntent = PendingIntent.getBroadcast( - context, - url.hashCode(), - intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); - - // Schedule the alarm with exact timing - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (alarmManager.canScheduleExactAlarms()) { - alarmManager.setExactAndAllowWhileIdle( - AlarmManager.RTC_WAKEUP, - calendar.getTimeInMillis(), - pendingIntent - ); - } else { - throw new SecurityException("Cannot schedule exact alarms"); - } - } else { - alarmManager.setExactAndAllowWhileIdle( - AlarmManager.RTC_WAKEUP, - calendar.getTimeInMillis(), - pendingIntent - ); - } - - // Set repeating alarm for subsequent days - alarmManager.setRepeating( - AlarmManager.RTC_WAKEUP, - calendar.getTimeInMillis() + TimeUnit.DAYS.toMillis(1), - TimeUnit.DAYS.toMillis(1), - pendingIntent - ); - - // Release wake lock after scheduling - releaseWakeLock(); - - // Store the scheduled time and URL for potential rescheduling - SharedPreferences.Editor editor = settings.edit(); - editor.putString("scheduled_time", time); - editor.putString("notification_url", url); - editor.putString("notification_title", call.getString("title", "Daily Notification")); - editor.putString("notification_body", call.getString("body", "Your daily update is ready")); - editor.apply(); - - logger.log("Notification scheduled for " + time, DailyNotificationLogger.Level.INFO); - call.resolve(); - - } catch (Exception e) { - releaseWakeLock(); - logger.log("Error scheduling notification: " + e.getMessage(), DailyNotificationLogger.Level.ERROR); - call.reject("Failed to schedule notification: " + e.getMessage()); - } - } - - @PluginMethod - public void getLastNotification(PluginCall call) { - try { - // TODO: Implement last notification retrieval from local storage - JSObject result = new JSObject(); - result.put("id", ""); - result.put("title", ""); - result.put("body", ""); - result.put("timestamp", 0); - call.resolve(result); - } catch (Exception e) { - logger.log("Error getting last notification: " + e.getMessage(), DailyNotificationLogger.Level.ERROR); - call.reject("Failed to get last notification: " + e.getMessage()); - } - } - - @PluginMethod - public void cancelAllNotifications(PluginCall call) { - try { - Intent intent = new Intent(context, DailyNotificationReceiver.class); - PendingIntent pendingIntent = PendingIntent.getBroadcast( - context, - 0, - intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); - alarmManager.cancel(pendingIntent); - notificationManager.cancelAll(); - logger.log("All notifications cancelled", DailyNotificationLogger.Level.INFO); - call.resolve(); - } catch (Exception e) { - logger.log("Error cancelling notifications: " + e.getMessage(), DailyNotificationLogger.Level.ERROR); - call.reject("Failed to cancel notifications: " + e.getMessage()); - } - } - - @PluginMethod - public void getNotificationStatus(PluginCall call) { - try { - JSObject result = new JSObject(); - result.put("nextNotificationTime", getNextNotificationTime()); - result.put("isEnabled", isNotificationEnabled()); - result.put("settings", getCurrentSettings()); - call.resolve(result); - } catch (Exception e) { - logger.log("Error getting notification status: " + e.getMessage(), DailyNotificationLogger.Level.ERROR); - call.reject("Failed to get notification status: " + e.getMessage()); - } - } - - @PluginMethod - public void updateSettings(PluginCall call) { - try { - SharedPreferences.Editor editor = settings.edit(); - - if (call.hasOption("sound")) { - editor.putBoolean("sound", call.getBoolean("sound")); - } - - if (call.hasOption("priority")) { - String priority = call.getString("priority"); - if (isValidPriority(priority)) { - editor.putString("priority", priority); - } else { - throw new IllegalArgumentException("Invalid priority value"); - } - } - - if (call.hasOption("timezone")) { - String timezone = call.getString("timezone"); - if (TimeZone.getTimeZone(timezone) != null) { - editor.putString("timezone", timezone); - } else { - throw new IllegalArgumentException("Invalid timezone"); - } - } - - editor.apply(); - logger.log("Settings updated successfully", DailyNotificationLogger.Level.INFO); - call.resolve(getCurrentSettings()); - } catch (Exception e) { - logger.log("Error updating settings: " + e.getMessage(), DailyNotificationLogger.Level.ERROR); - call.reject("Failed to update settings: " + e.getMessage()); - } - } - - private long getNextNotificationTime() { - // TODO: Implement next notification time retrieval - return 0; - } - - private boolean isNotificationEnabled() { - return notificationManager.areNotificationsEnabled(); - } - - private JSObject getCurrentSettings() { - JSObject settings = new JSObject(); - settings.put("sound", this.settings.getBoolean("sound", true)); - settings.put("priority", this.settings.getString("priority", "default")); - settings.put("timezone", this.settings.getString("timezone", TimeZone.getDefault().getID())); - return settings; - } - - private boolean isValidPriority(String priority) { - return priority != null && - (priority.equals("high") || - priority.equals("default") || - priority.equals("low")); - } - - private void initializeBatteryMonitoring() { - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_POWER_CONNECTED); - filter.addAction(Intent.ACTION_POWER_DISCONNECTED); - filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); - context.registerReceiver(batteryReceiver, filter); - updateBatteryStatus(); - updatePowerState(); - } - - private final BroadcastReceiver batteryReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - updateBatteryStatus(); - } - }; - - private void updateBatteryStatus() { - Intent batteryStatus = context.registerReceiver(null, - new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - - if (batteryStatus != null) { - int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); - float batteryPct = level * 100 / (float)scale; - - int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); - boolean charging = status == BatteryManager.BATTERY_HEALTH_CHARGING || - status == BatteryManager.BATTERY_HEALTH_FULL; - - batteryLevel = (int)batteryPct; - isCharging = charging; - lastBatteryCheck = System.currentTimeMillis(); - - logger.log(String.format("Battery status updated: %d%% (%s)", - batteryLevel, - isCharging ? "charging" : "not charging"), - DailyNotificationLogger.Level.DEBUG); - - // Store battery stats - SharedPreferences.Editor editor = settings.edit(); - editor.putInt("last_battery_level", batteryLevel); - editor.putBoolean("last_charging_state", isCharging); - editor.putLong("last_battery_check", lastBatteryCheck); - editor.apply(); - - // Adjust scheduling based on battery level if adaptive scheduling is enabled - if (adaptiveSchedulingEnabled) { - adjustSchedulingForBatteryLevel(); - } - } - } - - private void adjustSchedulingForBatteryLevel() { - if (batteryLevel < 15) { - // Critical battery level - reduce frequency - lastBatteryCheck = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(4); - } else if (batteryLevel < 30) { - // Low battery level - moderate frequency - lastBatteryCheck = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2); - } else if (batteryLevel < 50) { - // Medium battery level - standard frequency - lastBatteryCheck = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1); - } - } - - private void updatePowerState() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - powerState = powerManager.getPowerState(); - SharedPreferences.Editor editor = settings.edit(); - editor.putInt(POWER_STATE_KEY, powerState); - editor.apply(); - - // Adjust scheduling based on power state - if (adaptiveSchedulingEnabled) { - adjustSchedulingForPowerState(); - } - } - } - - private void adjustSchedulingForPowerState() { - if (powerState == PowerManager.POWER_STATE_SAVE) { - // In power save mode, increase the interval between checks - lastBatteryCheck = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2); - } else if (powerState == PowerManager.POWER_STATE_NORMAL) { - // In normal mode, use standard intervals - lastBatteryCheck = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(30); - } - } - - @PluginMethod - public void getBatteryStatus(PluginCall call) { - try { - // Check battery optimization status if needed - if (System.currentTimeMillis() - lastOptimizationCheck > BATTERY_CHECK_INTERVAL) { - checkBatteryOptimization(); - } - - JSObject result = new JSObject(); - result.put("level", batteryLevel); - result.put("isCharging", isCharging); - result.put("lastCheck", lastBatteryCheck); - result.put("isOptimizationExempt", isOptimizationExempt); - result.put("lastOptimizationCheck", lastOptimizationCheck); - result.put("isInDozeMode", isDeviceInDozeMode()); - call.resolve(result); - } catch (Exception e) { - logger.log("Error getting battery status: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - call.reject("Failed to get battery status: " + e.getMessage()); - } - } - - @Override - protected void handleOnDestroy() { - try { - context.unregisterReceiver(batteryReceiver); - } catch (Exception e) { - logger.log("Error unregistering battery receiver: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - } - super.handleOnDestroy(); - } - - public void rescheduleMissedNotifications() { - try { - long lastNotificationTime = settings.getLong(LAST_NOTIFICATION_KEY, 0); - long currentTime = System.currentTimeMillis(); - - // If more than 24 hours have passed since the last notification - if (currentTime - lastNotificationTime > TimeUnit.DAYS.toMillis(1)) { - logger.log("Missed notifications detected, rescheduling", DailyNotificationLogger.Level.WARNING); - - // Get the scheduled time from settings - String scheduledTime = settings.getString("scheduled_time", null); - if (scheduledTime != null) { - // Parse the scheduled time - String[] timeComponents = scheduledTime.split(":"); - if (timeComponents.length == 2) { - int hour = Integer.parseInt(timeComponents[0]); - int minute = Integer.parseInt(timeComponents[1]); - - // Create calendar instance for the scheduled time - Calendar calendar = Calendar.getInstance(); - calendar.set(Calendar.HOUR_OF_DAY, hour); - calendar.set(Calendar.MINUTE, minute); - calendar.set(Calendar.SECOND, 0); - - // If the time has already passed today, schedule for tomorrow - if (calendar.getTimeInMillis() <= currentTime) { - calendar.add(Calendar.DAY_OF_YEAR, 1); - } - - // Get the notification URL from settings - String url = settings.getString("notification_url", null); - if (url != null) { - // Create intent for the notification - Intent intent = new Intent(context, DailyNotificationReceiver.class); - intent.putExtra("url", url); - intent.putExtra("title", settings.getString("notification_title", "Daily Notification")); - intent.putExtra("body", settings.getString("notification_body", "Your daily update is ready")); - intent.putExtra("sound", settings.getBoolean("sound", true)); - intent.putExtra("priority", settings.getString("priority", "default")); - - PendingIntent pendingIntent = PendingIntent.getBroadcast( - context, - url.hashCode(), - intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); - - // Schedule the notification - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (alarmManager.canScheduleExactAlarms()) { - alarmManager.setExactAndAllowWhileIdle( - AlarmManager.RTC_WAKEUP, - calendar.getTimeInMillis(), - pendingIntent - ); - } - } else { - alarmManager.setExactAndAllowWhileIdle( - AlarmManager.RTC_WAKEUP, - calendar.getTimeInMillis(), - pendingIntent - ); - } - - logger.log("Missed notification rescheduled for " + scheduledTime, - DailyNotificationLogger.Level.INFO); - } - } - } - } - } catch (Exception e) { - logger.log("Error rescheduling missed notifications: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - } - } - - @PluginMethod - public void setAdaptiveScheduling(PluginCall call) { - try { - boolean enabled = call.getBoolean("enabled", true); - adaptiveSchedulingEnabled = enabled; - - SharedPreferences.Editor editor = settings.edit(); - editor.putBoolean(ADAPTIVE_SCHEDULING_KEY, enabled); - editor.apply(); - - logger.log("Adaptive scheduling " + (enabled ? "enabled" : "disabled"), - DailyNotificationLogger.Level.INFO); - call.resolve(); - } catch (Exception e) { - logger.log("Error setting adaptive scheduling: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - call.reject("Failed to set adaptive scheduling: " + e.getMessage()); - } - } - - @PluginMethod - public void getPowerState(PluginCall call) { - try { - JSObject result = new JSObject(); - result.put("powerState", powerState); - result.put("adaptiveScheduling", adaptiveSchedulingEnabled); - result.put("batteryLevel", batteryLevel); - result.put("isCharging", isCharging); - result.put("lastCheck", lastBatteryCheck); - call.resolve(result); - } catch (Exception e) { - logger.log("Error getting power state: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - call.reject("Failed to get power state: " + e.getMessage()); - } - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java b/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java deleted file mode 100644 index 0a7d05b..0000000 --- a/android/app/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java +++ /dev/null @@ -1,186 +0,0 @@ -/** - * DailyNotificationReceiver.java - * Daily Notification Plugin for Capacitor - * - * Broadcast receiver for handling daily notifications - * - * Features: - * - Notification display with custom actions - * - Rich notification content - * - Event broadcasting - * - Error recovery - * - Notification categories - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; - -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; - -public class DailyNotificationReceiver extends BroadcastReceiver { - private static final String TAG = "DailyNotificationReceiver"; - private DailyNotificationLogger logger; - - // Notification categories - private static final String CATEGORY_DAILY = "DAILY_NOTIFICATION"; - - // Action identifiers - private static final String ACTION_VIEW = "VIEW"; - private static final String ACTION_DISMISS = "DISMISS"; - private static final String ACTION_SNOOZE = "SNOOZE"; - - @Override - public void onReceive(Context context, Intent intent) { - logger = new DailyNotificationLogger(); - - try { - String url = intent.getStringExtra("url"); - String title = intent.getStringExtra("title"); - String body = intent.getStringExtra("body"); - boolean sound = intent.getBooleanExtra("sound", true); - String priority = intent.getStringExtra("priority"); - - if (url == null || title == null || body == null) { - throw new IllegalArgumentException("Missing required notification parameters"); - } - - showNotification(context, title, body, url, sound, priority); - logger.log("Notification displayed successfully", DailyNotificationLogger.Level.INFO); - - } catch (Exception e) { - logger.log("Error displaying notification: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR, e); - } - } - - private void showNotification(Context context, String title, String body, - String url, boolean sound, String priority) { - NotificationManager notificationManager = - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - - // Create notification channel if needed - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channel = new NotificationChannel( - DailyNotificationConstants.Channel.ID, - DailyNotificationConstants.Channel.NAME, - NotificationManager.IMPORTANCE_DEFAULT - ); - channel.setDescription(DailyNotificationConstants.Channel.DESCRIPTION); - channel.enableVibration(DailyNotificationConstants.Channel.ENABLE_VIBRATION); - channel.enableLights(DailyNotificationConstants.Channel.ENABLE_LIGHTS); - notificationManager.createNotificationChannel(channel); - } - - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, - DailyNotificationConstants.Channel.ID) - .setSmallIcon(android.R.drawable.ic_dialog_info) - .setContentTitle(title) - .setContentText(body) - .setPriority(getNotificationPriority(priority)) - .setCategory(CATEGORY_DAILY) - .setAutoCancel(true) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setStyle(new NotificationCompat.BigTextStyle().bigText(body)); - - if (sound) { - builder.setDefaults(NotificationCompat.DEFAULT_SOUND); - } - - // Create intent for notification tap - Intent contentIntent = new Intent(context, context.getClass()); - contentIntent.setData(Uri.parse(url)); - - PendingIntent pendingIntent = PendingIntent.getActivity( - context, - 0, - contentIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); - - builder.setContentIntent(pendingIntent); - - // Add custom actions - addCustomActions(builder, context, url); - - Notification notification = builder.build(); - - // Use NotificationManagerCompat for better compatibility - NotificationManagerCompat notificationManagerCompat = - NotificationManagerCompat.from(context); - - try { - notificationManagerCompat.notify(url.hashCode(), notification); - } catch (SecurityException e) { - logger.log("Security exception while showing notification: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - } - } - - private void addCustomActions(NotificationCompat.Builder builder, Context context, String url) { - // View action - Intent viewIntent = new Intent(context, context.getClass()); - viewIntent.setAction(ACTION_VIEW); - viewIntent.setData(Uri.parse(url)); - - PendingIntent viewPendingIntent = PendingIntent.getActivity( - context, - 1, - viewIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); - - builder.addAction(android.R.drawable.ic_menu_view, "View", viewPendingIntent); - - // Snooze action - Intent snoozeIntent = new Intent(context, context.getClass()); - snoozeIntent.setAction(ACTION_SNOOZE); - snoozeIntent.putExtra("url", url); - - PendingIntent snoozePendingIntent = PendingIntent.getBroadcast( - context, - 2, - snoozeIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); - - builder.addAction(android.R.drawable.ic_menu_revert, "Snooze", snoozePendingIntent); - - // Dismiss action - Intent dismissIntent = new Intent(context, context.getClass()); - dismissIntent.setAction(ACTION_DISMISS); - dismissIntent.putExtra("url", url); - - PendingIntent dismissPendingIntent = PendingIntent.getBroadcast( - context, - 3, - dismissIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); - - builder.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Dismiss", dismissPendingIntent); - } - - private int getNotificationPriority(String priority) { - switch (priority.toLowerCase()) { - case "high": - return NotificationCompat.PRIORITY_HIGH; - case "low": - return NotificationCompat.PRIORITY_LOW; - default: - return NotificationCompat.PRIORITY_DEFAULT; - } - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/example/app/MainActivity.java b/android/app/src/main/java/com/timesafari/dailynotification/MainActivity.java similarity index 69% rename from android/app/src/main/java/com/example/app/MainActivity.java rename to android/app/src/main/java/com/timesafari/dailynotification/MainActivity.java index 966f32d..7f561c0 100644 --- a/android/app/src/main/java/com/example/app/MainActivity.java +++ b/android/app/src/main/java/com/timesafari/dailynotification/MainActivity.java @@ -1,4 +1,4 @@ -package com.example.app; +package com.timesafari.dailynotification; import com.getcapacitor.BridgeActivity; diff --git a/android/app/src/main/java/com/timesafari/dailynotification/MaintenanceWorker.java b/android/app/src/main/java/com/timesafari/dailynotification/MaintenanceWorker.java deleted file mode 100644 index e6b0c07..0000000 --- a/android/app/src/main/java/com/timesafari/dailynotification/MaintenanceWorker.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.timesafari.dailynotification; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.work.Worker; -import androidx.work.WorkerParameters; - -public class MaintenanceWorker extends Worker { - private final DailyNotificationLogger logger; - - public MaintenanceWorker(@NonNull Context context, @NonNull WorkerParameters params) { - super(context, params); - logger = new DailyNotificationLogger(); - } - - @NonNull - @Override - public Result doWork() { - try { - logger.log("Maintenance work started", DailyNotificationLogger.Level.INFO); - - // Perform maintenance tasks - performMaintenance(); - - logger.log("Maintenance work completed successfully", DailyNotificationLogger.Level.INFO); - return Result.success(); - } catch (Exception e) { - logger.log("Error during maintenance work: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - return Result.failure(); - } - } - - private void performMaintenance() { - // Check and update battery optimization status - DailyNotificationPlugin plugin = DailyNotificationPlugin.getInstance(); - if (plugin != null) { - plugin.checkBatteryOptimization(); - } - - // Update battery status - if (plugin != null) { - plugin.updateBatteryStatus(); - } - - // Reschedule any missed notifications - if (plugin != null) { - plugin.rescheduleMissedNotifications(); - } - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/timesafari/dailynotification/README.md b/android/app/src/main/java/com/timesafari/dailynotification/README.md deleted file mode 100644 index e58d875..0000000 --- a/android/app/src/main/java/com/timesafari/dailynotification/README.md +++ /dev/null @@ -1,423 +0,0 @@ -# DailyNotification Android Implementation - -This directory contains the Android-specific implementation of the DailyNotification plugin for Capacitor. - -## Overview - -The DailyNotification plugin provides functionality for scheduling and managing daily notifications on Android devices. It uses Android's native notification system and background task scheduling to ensure reliable delivery of notifications. - -## Features - -- Daily notification scheduling with precise timing -- Notification channel management (Android 8.0+) -- Background task handling with WorkManager -- Battery optimization support -- Settings persistence -- Rich logging capabilities -- Comprehensive error handling -- Extensive test coverage - -## Architecture - -### Core Components - -1. **DailyNotificationPlugin** - - Main plugin class handling all notification operations - - Manages notification scheduling and settings - - Handles plugin method calls from JavaScript - -2. **DailyNotificationReceiver** - - Broadcast receiver for handling notification display - - Manages notification content and presentation - -3. **DailyNotificationLogger** - - Structured logging system - - Multiple log levels (DEBUG, INFO, WARNING, ERROR) - - Configurable logging behavior - -4. **DailyNotificationConstants** - - Centralized constants management - - Default values and configuration - - Error messages and keys - -5. **DailyNotificationConfig** - - Configuration management - - Settings persistence - - Default values - -### Key Features - -#### Notification Scheduling -- Uses AlarmManager for precise timing -- Handles timezone changes -- Supports multiple notifications per day -- Battery-optimized scheduling - -#### Background Processing -- WorkManager for reliable background tasks -- Battery optimization handling -- Network state monitoring -- Retry mechanism - -#### Settings Management -- Persistent storage using SharedPreferences -- Default values management -- Settings validation -- Real-time updates - -#### Error Handling -- Comprehensive error catching -- Detailed error messages -- Logging integration -- Recovery mechanisms - -## Testing - -### Test Structure - -1. **Unit Tests** - - Core functionality testing - - Settings management - - Time validation - - Error handling - -2. **Integration Tests** - - Notification scheduling - - Background tasks - - Settings persistence - - Event handling - -3. **Edge Cases** - - Timezone changes - - Battery optimization - - Network issues - - Resource cleanup - -### Running Tests - -```bash -# Run all tests -./gradlew test - -# Run specific test class -./gradlew test --tests "com.timesafari.dailynotification.DailyNotificationPluginTest" - -# Run with coverage -./gradlew test jacocoTestReport -``` - -## Security Considerations - -1. **Data Storage** - - Encrypted SharedPreferences for sensitive data - - Secure notification content handling - - Safe URL handling - -2. **Permissions** - - Runtime permission handling - - Permission validation - - Graceful degradation - -3. **Background Tasks** - - Battery optimization compliance - - Resource usage monitoring - - Task scheduling limits - -## Performance Optimization - -1. **Battery Usage** - - Efficient scheduling - - Background task optimization - - Wake lock management - -2. **Memory Management** - - Resource cleanup - - Memory leak prevention - - Cache management - -3. **Network Usage** - - Efficient data fetching - - Caching strategies - - Retry optimization - -## Best Practices - -1. **Code Organization** - - Clear package structure - - Consistent naming conventions - - Comprehensive documentation - -2. **Error Handling** - - Graceful degradation - - User-friendly messages - - Logging integration - -3. **Testing** - - Comprehensive test coverage - - Edge case handling - - Performance testing - -## Dependencies - -- AndroidX Core -- AndroidX AppCompat -- AndroidX WorkManager -- AndroidX Room -- AndroidX Lifecycle -- AndroidX Security -- JUnit -- Mockito - -## Contributing - -1. Fork the repository -2. Create a feature branch -3. Commit your changes -4. Push to the branch -5. Create a Pull Request - -## License - -This project is licensed under the MIT License - see the LICENSE file for details. - -## Author - -Matthew Raymer - -# Daily Notification Plugin - Battery Optimization Guide - -## Battery Overview - -The Daily Notification Plugin implements sophisticated battery optimization features to ensure reliable notification delivery while minimizing battery impact. This guide covers the implementation details and best practices for battery optimization. - -## Battery Optimization Features - -### 1. Adaptive Scheduling - -- Adjusts notification timing based on battery level and power state -- Reduces frequency during low battery conditions -- Optimizes wake lock duration based on battery status -- Implements smart rescheduling for missed notifications - -### 2. Power State Management - -- Monitors device power state (normal, power save, etc.) -- Adapts notification behavior based on power mode -- Implements Doze mode handling -- Manages wake locks efficiently - -### 3. Battery Level Monitoring - -- Real-time battery level tracking -- Charging state detection -- Battery health monitoring -- Adaptive thresholds based on battery status - -## API Methods - -### Battery Optimization - -```typescript -interface BatteryOptimizationStatus { - isExempt: boolean; - requestTime: number; - checkTime: number; -} - -// Request battery optimization exemption -requestBatteryOptimizationExemption(): Promise; - -// Get current battery status -getBatteryStatus(): Promise<{ - level: number; - isCharging: boolean; - lastCheck: number; - isOptimizationExempt: boolean; - lastOptimizationCheck: number; - isInDozeMode: boolean; -}>; - -// Get power state information -getPowerState(): Promise<{ - powerState: number; - adaptiveScheduling: boolean; - batteryLevel: number; - isCharging: boolean; - lastCheck: number; -}>; - -// Configure adaptive scheduling -setAdaptiveScheduling(enabled: boolean): Promise; -``` - -## Implementation Details - -### Battery Optimization Exemption - -```java -@PluginMethod -public void requestBatteryOptimizationExemption(PluginCall call) { - // Implementation details... -} -``` - -### Power State Monitoring - -```java -private void updatePowerState() { - // Implementation details... -} -``` - -### Adaptive Scheduling - -```java -private void adjustSchedulingForBatteryLevel() { - // Implementation details... -} -``` - -## Best Practices - -### 1. Battery Optimization Exemption -- Request exemption only when necessary -- Check exemption status before scheduling notifications -- Implement fallback mechanisms when exemption is not granted - -### 2. Wake Lock Usage -- Use minimal wake lock duration -- Release wake locks promptly -- Implement battery-aware wake lock duration - -### 3. Doze Mode Handling - -- Schedule maintenance windows during Doze mode -- Use WorkManager for reliable background tasks -- Implement backup alarms for critical notifications - -### 4. Battery Level Monitoring - -- Monitor battery level changes -- Adjust notification frequency based on battery status -- Implement battery-saving thresholds - -### 5. Power State Adaptation - -- Adapt to power save mode -- Optimize resource usage during low power -- Implement graceful degradation - -## Battery Error Handling - -### Battery Optimization during Error Handling - -```java -try { - // Battery optimization code -} catch (Exception e) { - logger.log("Error in battery optimization: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - // Handle error appropriately -} -``` - -### Wake Lock - -```java -try { - // Wake lock code -} catch (Exception e) { - logger.log("Error managing wake lock: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - // Handle error appropriately -} -``` - -### Doze Mode - -```java -try { - // Doze mode handling code -} catch (Exception e) { - logger.log("Error handling Doze mode: " + e.getMessage(), - DailyNotificationLogger.Level.ERROR); - // Handle error appropriately -} -``` - -## Performance Considerations - -### Battery Impact - -- Minimize wake lock usage -- Optimize background task scheduling -- Implement efficient battery monitoring - -### Resource Usage - -- Use WorkManager for background tasks -- Implement efficient wake lock management -- Optimize notification scheduling - -### Storage - -- Efficient battery stats storage -- Optimized settings persistence -- Minimal logging overhead - -## Security - -### Wake Lock Security - -- Use appropriate wake lock flags -- Implement timeout mechanisms -- Handle wake lock failures gracefully - -### Data Security - -- Secure storage of battery stats -- Protected settings access -- Safe logging practices - -## Testing - -### Battery Optimization Tests - -```java -@Test -void testBatteryOptimizationExemptionRequest() { - // Test implementation -} -``` - -### Power State Tests - -```java -@Test -void testPowerStateMonitoring() { - // Test implementation -} -``` - -### Performance Tests -```java -@Test -void testBatteryImpact() { - // Test implementation -} -``` - -## Contributing - -### Documentation -- Keep documentation up to date -- Document all battery optimization features -- Include usage examples - -### Code -- Follow battery optimization best practices -- Implement comprehensive tests -- Maintain backward compatibility - -## License -MIT License - See LICENSE file for details \ No newline at end of file diff --git a/android/app/src/main/java/index.ts b/android/app/src/main/java/index.ts deleted file mode 100644 index 0686b27..0000000 --- a/android/app/src/main/java/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Android implementation of the DailyNotification plugin - * @module DailyNotificationAndroid - */ - -import { Capacitor } from '@capacitor/core'; -import type { DailyNotificationPlugin, DailyNotificationOptions, PermissionStatus } from '../definitions'; - -export class DailyNotificationAndroid implements DailyNotificationPlugin { - private options: DailyNotificationOptions = { - url: '', - notificationTime: '09:00', - title: 'Daily Update', - body: 'Your daily notification is ready' - }; - - /** - * Initialize the daily notification system for Android - * @param options Configuration options for the notification system - */ - async initialize(options: DailyNotificationOptions): Promise { - if (Capacitor.getPlatform() !== 'android') { - throw new Error('This implementation is for Android only'); - } - this.options = options; - // TODO: Implement Android-specific initialization - } - - /** - * Check current permission status for notifications - * @returns Current permission status - */ - async checkPermissions(): Promise { - if (Capacitor.getPlatform() !== 'android') { - throw new Error('This implementation is for Android only'); - } - // TODO: Implement Android-specific permission check - return { notifications: 'prompt' }; - } - - /** - * Request notification permissions from the user - * @returns Updated permission status after request - */ - async requestPermissions(): Promise { - if (Capacitor.getPlatform() !== 'android') { - throw new Error('This implementation is for Android only'); - } - // TODO: Implement Android-specific permission request - return { notifications: 'prompt' }; - } -} \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 669f043..414ed79 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,7 +1,7 @@ - capacitor-daily-notification - capacitor-daily-notification - com.example.app - com.example.app + DailyNotificationPlugin + DailyNotificationPlugin + com.timesafari.dailynotification + com.timesafari.dailynotification diff --git a/android/app/src/test/java/com/timesafari/dailynotification/BatteryOptimizationSettingsTest.java b/android/app/src/test/java/com/timesafari/dailynotification/BatteryOptimizationSettingsTest.java deleted file mode 100644 index dce621b..0000000 --- a/android/app/src/test/java/com/timesafari/dailynotification/BatteryOptimizationSettingsTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/** - * BatteryOptimizationSettingsTest.java - * Daily Notification Plugin for Capacitor - * - * Tests for battery optimization settings and power state management - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.BatteryManager; -import android.os.Build; -import android.os.PowerManager; -import android.provider.Settings; - -import androidx.work.WorkManager; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -@RunWith(RobolectricTestRunner.class) -@Config(sdk = {Build.VERSION_CODES.M}) -public class BatteryOptimizationSettingsTest { - - @Mock - private Context context; - - @Mock - private SharedPreferences settings; - - @Mock - private SharedPreferences.Editor editor; - - @Mock - private PowerManager powerManager; - - @Mock - private WorkManager workManager; - - private BatteryOptimizationSettings batterySettings; - - @Before - public void setUp() { - MockitoAnnotations.openMocks(this); - - when(context.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); - when(settings.edit()).thenReturn(editor); - when(editor.putBoolean(anyString(), anyBoolean())).thenReturn(editor); - when(editor.putInt(anyString(), anyInt())).thenReturn(editor); - when(editor.putLong(anyString(), anyLong())).thenReturn(editor); - when(editor.apply()).thenReturn(true); - - batterySettings = new BatteryOptimizationSettings(context, settings); - } - - @Test - public void testRequestBatteryOptimizationExemption() { - // Test when already exempt - when(settings.getBoolean(anyString(), anyBoolean())).thenReturn(true); - assertTrue(batterySettings.requestBatteryOptimizationExemption()); - - // Test when not exempt - when(settings.getBoolean(anyString(), anyBoolean())).thenReturn(false); - when(context.getPackageName()).thenReturn("com.test.app"); - - Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(android.net.Uri.parse("package:com.test.app")); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - batterySettings.requestBatteryOptimizationExemption(); - - verify(context).startActivity(eq(intent)); - verify(editor).putLong(eq("last_optimization_request"), anyLong()); - } - - @Test - public void testUpdatePowerState() { - when(powerManager.getPowerState()).thenReturn(PowerManager.POWER_STATE_SAVE); - - batterySettings.updatePowerState(); - - verify(editor).putInt(eq("power_state"), eq(PowerManager.POWER_STATE_SAVE)); - } - - @Test - public void testUpdateBatteryStatus() { - Intent batteryStatus = new Intent(Intent.ACTION_BATTERY_CHANGED); - batteryStatus.putExtra(BatteryManager.EXTRA_LEVEL, 80); - batteryStatus.putExtra(BatteryManager.EXTRA_SCALE, 100); - batteryStatus.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_HEALTH_CHARGING); - - when(context.registerReceiver(any(), any())).thenReturn(batteryStatus); - - batterySettings.updateBatteryStatus(); - - assertEquals(80, batterySettings.getBatteryLevel()); - assertTrue(batterySettings.isCharging()); - verify(editor).putInt(eq("last_battery_level"), eq(80)); - verify(editor).putBoolean(eq("last_charging_state"), eq(true)); - } - - @Test - public void testAdjustSchedulingForPowerState() { - // Test power save mode - when(powerManager.getPowerState()).thenReturn(PowerManager.POWER_STATE_SAVE); - batterySettings.updatePowerState(); - assertTrue(batterySettings.shouldCheckBattery()); - - // Test normal mode - when(powerManager.getPowerState()).thenReturn(PowerManager.POWER_STATE_NORMAL); - batterySettings.updatePowerState(); - assertTrue(batterySettings.shouldCheckBattery()); - - // Test optimized mode - when(powerManager.getPowerState()).thenReturn(PowerManager.POWER_STATE_OPTIMIZED); - batterySettings.updatePowerState(); - assertTrue(batterySettings.shouldCheckBattery()); - } - - @Test - public void testGetPowerStateString() { - assertEquals("POWER_SAVE", batterySettings.getPowerStateString(PowerManager.POWER_STATE_SAVE)); - assertEquals("NORMAL", batterySettings.getPowerStateString(PowerManager.POWER_STATE_NORMAL)); - assertEquals("OPTIMIZED", batterySettings.getPowerStateString(PowerManager.POWER_STATE_OPTIMIZED)); - assertEquals("UNKNOWN", batterySettings.getPowerStateString(999)); - } - - @Test - public void testShouldCheckBattery() { - // Test when check is needed - assertTrue(batterySettings.shouldCheckBattery()); - - // Test when check is not needed - when(settings.getLong(anyString(), anyLong())).thenReturn(System.currentTimeMillis()); - assertFalse(batterySettings.shouldCheckBattery()); - } -} \ No newline at end of file diff --git a/android/app/src/test/java/com/timesafari/dailynotification/DailyNotificationLoggerTest.java b/android/app/src/test/java/com/timesafari/dailynotification/DailyNotificationLoggerTest.java deleted file mode 100644 index a94c4a5..0000000 --- a/android/app/src/test/java/com/timesafari/dailynotification/DailyNotificationLoggerTest.java +++ /dev/null @@ -1,134 +0,0 @@ -/** - * DailyNotificationLoggerTest.java - * Daily Notification Plugin for Capacitor - * - * Tests for logging functionality - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import static org.junit.Assert.*; - -@RunWith(RobolectricTestRunner.class) -public class DailyNotificationLoggerTest { - - private DailyNotificationLogger logger; - - @Before - public void setUp() { - logger = new DailyNotificationLogger(); - } - - @Test - public void testLogWithInfoLevel() { - // Test info level logging - logger.log("Test info message", DailyNotificationLogger.Level.INFO); - - // Verify log level - assertEquals(DailyNotificationLogger.Level.INFO, logger.getLastLogLevel()); - - // Verify message - assertTrue(logger.getLastLogMessage().contains("Test info message")); - } - - @Test - public void testLogWithWarningLevel() { - // Test warning level logging - logger.log("Test warning message", DailyNotificationLogger.Level.WARNING); - - // Verify log level - assertEquals(DailyNotificationLogger.Level.WARNING, logger.getLastLogLevel()); - - // Verify message - assertTrue(logger.getLastLogMessage().contains("Test warning message")); - } - - @Test - public void testLogWithErrorLevel() { - // Test error level logging - logger.log("Test error message", DailyNotificationLogger.Level.ERROR); - - // Verify log level - assertEquals(DailyNotificationLogger.Level.ERROR, logger.getLastLogLevel()); - - // Verify message - assertTrue(logger.getLastLogMessage().contains("Test error message")); - } - - @Test - public void testLogWithDebugLevel() { - // Test debug level logging - logger.log("Test debug message", DailyNotificationLogger.Level.DEBUG); - - // Verify log level - assertEquals(DailyNotificationLogger.Level.DEBUG, logger.getLastLogLevel()); - - // Verify message - assertTrue(logger.getLastLogMessage().contains("Test debug message")); - } - - @Test - public void testLogWithException() { - // Create test exception - Exception testException = new RuntimeException("Test exception"); - - // Test logging with exception - logger.log("Test error with exception", DailyNotificationLogger.Level.ERROR, testException); - - // Verify log level - assertEquals(DailyNotificationLogger.Level.ERROR, logger.getLastLogLevel()); - - // Verify message contains exception details - assertTrue(logger.getLastLogMessage().contains("Test error with exception")); - assertTrue(logger.getLastLogMessage().contains("Test exception")); - } - - @Test - public void testLogWithNullMessage() { - // Test logging with null message - logger.log(null, DailyNotificationLogger.Level.INFO); - - // Verify log level - assertEquals(DailyNotificationLogger.Level.INFO, logger.getLastLogLevel()); - - // Verify message is empty - assertTrue(logger.getLastLogMessage().isEmpty()); - } - - @Test - public void testLogWithNullLevel() { - // Test logging with null level - logger.log("Test message", null); - - // Verify default level is INFO - assertEquals(DailyNotificationLogger.Level.INFO, logger.getLastLogLevel()); - - // Verify message - assertTrue(logger.getLastLogMessage().contains("Test message")); - } - - @Test - public void testLogWithTimestamp() { - // Test logging with timestamp - logger.log("Test message with timestamp", DailyNotificationLogger.Level.INFO); - - // Verify message contains timestamp - assertTrue(logger.getLastLogMessage().matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.*")); - } - - @Test - public void testLogWithTag() { - // Test logging with tag - logger.log("Test message with tag", DailyNotificationLogger.Level.INFO, "TestTag"); - - // Verify message contains tag - assertTrue(logger.getLastLogMessage().contains("[TestTag]")); - } -} \ No newline at end of file diff --git a/android/app/src/test/java/com/timesafari/dailynotification/DailyNotificationPluginTest.java b/android/app/src/test/java/com/timesafari/dailynotification/DailyNotificationPluginTest.java deleted file mode 100644 index 2f78b85..0000000 --- a/android/app/src/test/java/com/timesafari/dailynotification/DailyNotificationPluginTest.java +++ /dev/null @@ -1,194 +0,0 @@ -/** - * DailyNotificationPluginTest.java - * Daily Notification Plugin for Capacitor - * - * Tests for the main plugin functionality - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import android.app.AlarmManager; -import android.app.NotificationManager; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Build; -import android.os.PowerManager; - -import com.getcapacitor.JSObject; -import com.getcapacitor.PluginCall; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -@RunWith(RobolectricTestRunner.class) -@Config(sdk = {Build.VERSION_CODES.M}) -public class DailyNotificationPluginTest { - - @Mock - private Context context; - - @Mock - private SharedPreferences settings; - - @Mock - private SharedPreferences.Editor editor; - - @Mock - private NotificationManager notificationManager; - - @Mock - private AlarmManager alarmManager; - - @Mock - private PowerManager powerManager; - - @Mock - private PluginCall pluginCall; - - private DailyNotificationPlugin plugin; - - @Before - public void setUp() { - MockitoAnnotations.openMocks(this); - - when(context.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(notificationManager); - when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(alarmManager); - when(context.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); - when(settings.edit()).thenReturn(editor); - when(editor.putBoolean(anyString(), anyBoolean())).thenReturn(editor); - when(editor.putString(anyString(), anyString())).thenReturn(editor); - when(editor.putLong(anyString(), anyLong())).thenReturn(editor); - when(editor.apply()).thenReturn(true); - - plugin = new DailyNotificationPlugin(); - plugin.load(); - } - - @Test - public void testScheduleDailyNotification() { - // Setup test data - when(pluginCall.getString("url")).thenReturn("https://example.com"); - when(pluginCall.getString("time")).thenReturn("09:00"); - when(pluginCall.getString("title")).thenReturn("Test Notification"); - when(pluginCall.getString("body")).thenReturn("Test Body"); - when(pluginCall.getBoolean("sound", true)).thenReturn(true); - when(pluginCall.getString("priority", "default")).thenReturn("default"); - - // Execute - plugin.scheduleDailyNotification(pluginCall); - - // Verify - verify(alarmManager).setExactAndAllowWhileIdle( - eq(AlarmManager.RTC_WAKEUP), - anyLong(), - any() - ); - verify(editor).putString(eq("scheduled_time"), eq("09:00")); - verify(editor).putString(eq("notification_url"), eq("https://example.com")); - } - - @Test - public void testCancelAllNotifications() { - // Execute - plugin.cancelAllNotifications(pluginCall); - - // Verify - verify(alarmManager).cancel(any()); - verify(notificationManager).cancelAll(); - } - - @Test - public void testGetNotificationStatus() { - // Setup - when(notificationManager.areNotificationsEnabled()).thenReturn(true); - - // Execute - plugin.getNotificationStatus(pluginCall); - - // Verify - verify(pluginCall).resolve(any(JSObject.class)); - } - - @Test - public void testUpdateSettings() { - // Setup - when(pluginCall.hasOption("sound")).thenReturn(true); - when(pluginCall.getBoolean("sound")).thenReturn(false); - when(pluginCall.hasOption("priority")).thenReturn(true); - when(pluginCall.getString("priority")).thenReturn("high"); - - // Execute - plugin.updateSettings(pluginCall); - - // Verify - verify(editor).putBoolean("sound", false); - verify(editor).putString("priority", "high"); - } - - @Test - public void testRequestBatteryOptimizationExemption() { - // Execute - plugin.requestBatteryOptimizationExemption(pluginCall); - - // Verify - verify(context).startActivity(any(Intent.class)); - } - - @Test - public void testGetBatteryStatus() { - // Execute - plugin.getBatteryStatus(pluginCall); - - // Verify - verify(pluginCall).resolve(any(JSObject.class)); - } - - @Test - public void testSetAdaptiveScheduling() { - // Setup - when(pluginCall.getBoolean("enabled", true)).thenReturn(true); - - // Execute - plugin.setAdaptiveScheduling(pluginCall); - - // Verify - verify(editor).putBoolean(eq("adaptive_scheduling"), eq(true)); - } - - @Test - public void testGetPowerState() { - // Execute - plugin.getPowerState(pluginCall); - - // Verify - verify(pluginCall).resolve(any(JSObject.class)); - } - - @Test - public void testHandleDozeMode() { - // Setup - when(powerManager.isDeviceIdleMode()).thenReturn(true); - - // Execute - plugin.handleDozeMode(); - - // Verify - verify(alarmManager).setAndAllowWhileIdle( - eq(AlarmManager.RTC_WAKEUP), - anyLong(), - any() - ); - } -} \ No newline at end of file diff --git a/android/app/src/test/java/com/timesafari/dailynotification/DailyNotificationReceiverTest.java b/android/app/src/test/java/com/timesafari/dailynotification/DailyNotificationReceiverTest.java deleted file mode 100644 index 88a272e..0000000 --- a/android/app/src/test/java/com/timesafari/dailynotification/DailyNotificationReceiverTest.java +++ /dev/null @@ -1,162 +0,0 @@ -/** - * DailyNotificationReceiverTest.java - * Daily Notification Plugin for Capacitor - * - * Tests for notification receiver functionality - * - * @author Matthew Raymer - */ - -package com.timesafari.dailynotification; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.content.Context; -import android.content.Intent; -import android.os.Build; - -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -@RunWith(RobolectricTestRunner.class) -@Config(sdk = {Build.VERSION_CODES.M}) -public class DailyNotificationReceiverTest { - - @Mock - private Context context; - - @Mock - private NotificationManager notificationManager; - - @Mock - private NotificationManagerCompat notificationManagerCompat; - - private DailyNotificationReceiver receiver; - - @Before - public void setUp() { - MockitoAnnotations.openMocks(this); - - when(context.getSystemService(Context.NOTIFICATION_SERVICE)) - .thenReturn(notificationManager); - when(NotificationManagerCompat.from(context)) - .thenReturn(notificationManagerCompat); - - receiver = new DailyNotificationReceiver(); - } - - @Test - public void testOnReceive() { - // Setup test data - Intent intent = new Intent(); - intent.putExtra("url", "https://example.com"); - intent.putExtra("title", "Test Notification"); - intent.putExtra("body", "Test Body"); - intent.putExtra("sound", true); - intent.putExtra("priority", "high"); - - // Execute - receiver.onReceive(context, intent); - - // Verify - verify(notificationManagerCompat).notify( - eq("https://example.com".hashCode()), - any(Notification.class) - ); - } - - @Test - public void testOnReceiveWithMissingParameters() { - // Setup test data - Intent intent = new Intent(); - - // Execute - receiver.onReceive(context, intent); - - // Verify - verify(notificationManagerCompat, never()).notify(anyInt(), any(Notification.class)); - } - - @Test - public void testShowNotification() { - // Setup test data - String title = "Test Notification"; - String body = "Test Body"; - String url = "https://example.com"; - boolean sound = true; - String priority = "high"; - - // Execute - receiver.showNotification(context, title, body, url, sound, priority); - - // Verify - verify(notificationManagerCompat).notify( - eq(url.hashCode()), - any(Notification.class) - ); - } - - @Test - public void testAddCustomActions() { - // Setup test data - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "test_channel"); - String url = "https://example.com"; - - // Execute - receiver.addCustomActions(builder, context, url); - - // Verify - verify(context, times(3)).getClass(); - } - - @Test - public void testGetNotificationPriority() { - // Test high priority - assertEquals( - NotificationCompat.PRIORITY_HIGH, - receiver.getNotificationPriority("high") - ); - - // Test low priority - assertEquals( - NotificationCompat.PRIORITY_LOW, - receiver.getNotificationPriority("low") - ); - - // Test default priority - assertEquals( - NotificationCompat.PRIORITY_DEFAULT, - receiver.getNotificationPriority("default") - ); - - // Test unknown priority - assertEquals( - NotificationCompat.PRIORITY_DEFAULT, - receiver.getNotificationPriority("unknown") - ); - } - - @Test - public void testCreateNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - // Execute - receiver.createNotificationChannel(context); - - // Verify - verify(notificationManager).createNotificationChannel(any(NotificationChannel.class)); - } - } -} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 1d3e084..9cc72cb 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,109 +1,29 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.0.0' + classpath 'com.google.gms:google-services:4.3.15' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files } } apply from: "variables.gradle" -apply plugin: 'com.android.library' - -ext { - compileSdkVersion = 33 - minSdkVersion = 21 - targetSdkVersion = 33 - buildToolsVersion = '33.0.0' -} - -android { - namespace "com.timesafari.dailynotification" - compileSdkVersion project.ext.compileSdkVersion - buildToolsVersion project.ext.buildToolsVersion - defaultConfig { - minSdkVersion project.ext.minSdkVersion - targetSdkVersion project.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - lintOptions { - abortOnError false - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - testOptions { - unitTests { - includeAndroidResources = true - } - } -} - allprojects { - tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:unchecked" - options.compilerArgs << "-Xlint:deprecation" + repositories { + google() + mavenCentral() } } -repositories { - google() - mavenCentral() - maven { url "https://jitpack.io" } - maven { - url "${project.rootDir}/capacitor-cordova-android-plugins/src/main/libs" - } - maven { - url "${project.rootDir}/libs" - } +task clean(type: Delete) { + delete rootProject.buildDir } - -dependencies { - // AndroidX Core - implementation 'androidx.core:core:1.7.0' - implementation 'androidx.core:core-ktx:1.7.0' - - // WorkManager for background tasks - implementation 'androidx.work:work-runtime:2.7.1' - - // Capacitor dependencies - implementation project(':capacitor-android') - implementation project(':capacitor-core') - - // Testing dependencies - testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:4.5.1' - testImplementation 'org.robolectric:robolectric:4.8' - testImplementation 'androidx.test:core:1.4.0' - testImplementation 'androidx.test:runner:1.4.0' - testImplementation 'androidx.test.ext:junit:1.1.3' - - // AndroidX Test - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - - // AndroidX AppCompat - implementation 'androidx.appcompat:appcompat:1.6.1' - - // AndroidX Core App - implementation 'androidx.core:core-app:1.0.0' - - // AndroidX Core AppCompat - implementation 'androidx.core:core-appcompat:1.0.0' - - // AndroidX Core AppCompat Resources - implementation 'androidx.core:core-appcompat-resources:1.0.0' -} \ No newline at end of file diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index ebdcd0c..9a5fa87 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -1,3 +1,3 @@ // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN include ':capacitor-android' -project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') \ No newline at end of file +project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') diff --git a/android/gradlew.bat b/android/gradlew.bat index 6689b85..93e3f59 100644 --- a/android/gradlew.bat +++ b/android/gradlew.bat @@ -1,92 +1,92 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/settings.gradle b/android/settings.gradle index b29c759..3b4431d 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,10 +1,5 @@ -rootProject.name = 'daily-notification' - include ':app' -include ':capacitor-android' include ':capacitor-cordova-android-plugins' - -project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') -project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins') +project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') apply from: 'capacitor.settings.gradle' \ No newline at end of file diff --git a/android/variables.gradle b/android/variables.gradle index ba92956..5946ada 100644 --- a/android/variables.gradle +++ b/android/variables.gradle @@ -1,14 +1,13 @@ ext { - compileSdkVersion = 33 minSdkVersion = 22 + compileSdkVersion = 33 targetSdkVersion = 33 - buildToolsVersion = '33.0.0' + androidxActivityVersion = '1.7.0' androidxAppCompatVersion = '1.6.1' androidxCoordinatorLayoutVersion = '1.2.0' - coreSplashScreenVersion = '1.0.1' - androidxActivityVersion = '1.7.0' androidxCoreVersion = '1.10.0' androidxFragmentVersion = '1.5.6' + coreSplashScreenVersion = '1.0.0' androidxWebkitVersion = '1.6.1' junitVersion = '4.13.2' androidxJunitVersion = '1.1.5' diff --git a/capacitor.config.ts b/capacitor.config.ts index 4ab66ee..7a40700 100644 --- a/capacitor.config.ts +++ b/capacitor.config.ts @@ -1,8 +1,8 @@ import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { - appId: 'com.example.app', - appName: 'capacitor-daily-notification', + appId: 'com.timesafari.dailynotification', + appName: 'DailyNotificationPlugin', webDir: 'www', server: { androidScheme: 'https' diff --git a/package-lock.json b/package-lock.json index ba7b97b..0d26bf9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,34 +1,32 @@ { - "name": "capacitor-daily-notification", - "version": "0.0.1", + "name": "@timesafari/daily-notification-plugin", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "capacitor-daily-notification", - "version": "0.0.1", - "hasInstallScript": true, + "name": "@timesafari/daily-notification-plugin", + "version": "1.0.0", "license": "MIT", "dependencies": { - "@capacitor/android": "^5.0.0", - "@capacitor/core": "^5.0.0", - "@capacitor/ios": "^5.0.0" + "@capacitor/core": "^5.7.8" }, "devDependencies": { + "@capacitor/android": "^5.7.8", "@capacitor/cli": "^5.0.0", - "@jest/globals": "^29.5.0", - "@types/jest": "^29.5.14", - "@types/node": "^20.0.0", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", - "eslint": "^8.0.0", + "@capacitor/ios": "^5.0.0", + "@types/jest": "^29.5.0", + "@types/node": "^18.15.0", + "@typescript-eslint/eslint-plugin": "^5.57.0", + "@typescript-eslint/parser": "^5.57.0", + "eslint": "^8.37.0", "jest": "^29.5.0", - "rimraf": "^5.0.0", + "prettier": "^2.8.7", + "rimraf": "^4.4.0", + "rollup": "^3.20.0", + "rollup-plugin-typescript2": "^0.31.0", "ts-jest": "^29.1.0", "typescript": "^5.0.0" - }, - "peerDependencies": { - "@capacitor/core": "^5.0.0" } }, "node_modules/@ampproject/remapping": { @@ -582,6 +580,7 @@ "version": "5.7.8", "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-5.7.8.tgz", "integrity": "sha512-ooWclwcuW0dy3YfqgoozkHkjatX8H2fb2/RwRsJa3cew1P1lUXIXri3Dquuy4LdqFAJA7UHcJ19Bl/6UKdsZYA==", + "dev": true, "license": "MIT", "peerDependencies": { "@capacitor/core": "^5.7.0" @@ -620,80 +619,6 @@ "node": ">=16.0.0" } }, - "node_modules/@capacitor/cli/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@capacitor/cli/node_modules/glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@capacitor/cli/node_modules/minimatch": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", - "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@capacitor/cli/node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/@capacitor/cli/node_modules/rimraf": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", - "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^9.2.0" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@capacitor/core": { "version": "5.7.8", "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.7.8.tgz", @@ -707,6 +632,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@capacitor/ios/-/ios-5.0.0.tgz", "integrity": "sha512-b1edQNe1cKiqnxoIR5WxbVjDlf3RWr2ZjaDxwEuBzwBAvvrFcweKdbw1ij45DWHKODaIymWoyAlAUN+vFOF5sw==", + "dev": true, "license": "MIT", "peerDependencies": { "@capacitor/core": "^5.0.0" @@ -987,109 +913,6 @@ "node": ">=16.0.0" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1612,15 +1435,18 @@ "node": ">= 8" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, "engines": { - "node": ">=14" + "node": ">= 8.0.0" } }, "node_modules/@sinclair/typebox": { @@ -1761,19 +1587,19 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.17.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.27.tgz", - "integrity": "sha512-U58sbKhDrthHlxHRJw7ZLiLDZGmAUOZUbpw0S6nL27sYUdhvgBLCRu/keSd6qcTsfArd1sRFCCBxzWATGr/0UA==", + "version": "18.19.84", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.84.tgz", + "integrity": "sha512-ACYy2HGcZPHxEeWTqowTF7dhXN+JU1o7Gr4b41klnn6pj2LD6rsiGqSZojMdk1Jh2ys3m76ap+ae1vvE4+5+vg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~5.26.4" } }, "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", "dev": true, "license": "MIT" }, @@ -1809,34 +1635,33 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1845,27 +1670,26 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1874,17 +1698,17 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -1892,26 +1716,26 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "tsutils": "^3.21.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "*" }, "peerDependenciesMeta": { "typescript": { @@ -1920,13 +1744,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -1934,23 +1758,22 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.3.7", + "tsutils": "^3.21.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -1962,70 +1785,45 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -2049,6 +1847,31 @@ "node": ">=10.0.0" } }, + "node_modules/@yarn-tool/resolve-package": { + "version": "1.0.47", + "resolved": "https://registry.npmjs.org/@yarn-tool/resolve-package/-/resolve-package-1.0.47.tgz", + "integrity": "sha512-Zaw58gQxjQceJqhqybJi1oUDaORT8i2GTgwICPs8v/X/Pkx35FXQba69ldHVg5pQZ6YLKpROXgyHvBaCJOFXiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "pkg-dir": "< 6 >= 5", + "tslib": "^2", + "upath2": "^3.1.13" + } + }, + "node_modules/@yarn-tool/resolve-package/node_modules/pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -2640,6 +2463,13 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2797,13 +2627,6 @@ "node": ">=6.0.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -2960,6 +2783,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -3078,6 +2915,23 @@ "node": ">=4.0" } }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3278,6 +3132,50 @@ "node": ">=8" } }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-cache-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3356,36 +3254,6 @@ "dev": true, "license": "ISC" }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -3504,21 +3372,19 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "dev": true, "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3548,9 +3414,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", "dev": true, "license": "ISC", "dependencies": { @@ -3946,22 +3812,6 @@ "node": ">=8" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/jake": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", @@ -4871,13 +4721,13 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", "dev": true, "license": "ISC", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=8" } }, "node_modules/minizlib": { @@ -4960,6 +4810,13 @@ "dev": true, "license": "MIT" }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true, + "license": "MIT" + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5101,13 +4958,6 @@ "node": ">=6" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5160,6 +5010,16 @@ "node": ">=0.10.0" } }, + "node_modules/path-is-network-drive": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/path-is-network-drive/-/path-is-network-drive-1.0.21.tgz", + "integrity": "sha512-B1PzE3CgxNKY0/69Urjw3KNi4K+4q4IBsvq02TwURsdNLZj2YUn0HGw2o26IrGV4YUffg7IHZiwKJ/EDhXMQyg==", + "dev": true, + "license": "ISC", + "dependencies": { + "tslib": "^2" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -5194,6 +5054,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/path-strip-sep": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/path-strip-sep/-/path-strip-sep-1.0.18.tgz", + "integrity": "sha512-IGC/vjHigvKV9RsE4ArZiGNkqNrb0tk1j/HO0TS49duEUqGSy1y464XhCWyTLFwqe7w7wFsdCX9fqUmAHoUaxA==", + "dev": true, + "license": "ISC", + "dependencies": { + "tslib": "^2" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5335,6 +5215,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -5543,21 +5439,75 @@ } }, "node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", + "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", "dev": true, "license": "ISC", "dependencies": { - "glob": "^10.3.7" + "glob": "^9.2.0" }, "bin": { - "rimraf": "dist/esm/bin.mjs" + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-typescript2": { + "version": "0.31.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.31.2.tgz", + "integrity": "sha512-hRwEYR1C8xDGVVMFJQdEVnNAeWRvpaY97g5mp3IeLnzhNXzSVq78Ye/BJ9PAaUfN4DXa/uDnqerifMOaMFY54Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^4.1.2", + "@yarn-tool/resolve-package": "^1.0.40", + "find-cache-dir": "^3.3.2", + "fs-extra": "^10.0.0", + "resolve": "^1.20.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "rollup": ">=1.26.3", + "typescript": ">=2.4.0" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5788,22 +5738,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -5817,20 +5751,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -6002,19 +5922,6 @@ "tree-kill": "cli.js" } }, - "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, "node_modules/ts-jest": { "version": "29.3.0", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.0.tgz", @@ -6084,6 +5991,29 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6135,9 +6065,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true, "license": "MIT" }, @@ -6161,6 +6091,19 @@ "node": ">=8" } }, + "node_modules/upath2": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/upath2/-/upath2-3.1.20.tgz", + "integrity": "sha512-g+t9q+MrIsX60eJzF4I/YNYmRmrT0HJnnEaenbUy/FFO1lY04YQoiJ/qS4Ou+a+D9WUPxN0cVUYXkkX9b1EAMw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/node": "*", + "path-is-network-drive": "^1.0.21", + "path-strip-sep": "^1.0.18", + "tslib": "^2" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -6278,25 +6221,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index d14aa44..f5cf38c 100644 --- a/package.json +++ b/package.json @@ -1,65 +1,53 @@ { - "name": "capacitor-daily-notification", - "version": "0.0.1", - "description": "Capacitor plugin for daily notifications with network content", + "name": "@timesafari/daily-notification-plugin", + "version": "1.0.0", + "description": "A Capacitor plugin for scheduling and managing daily notifications on Android devices", "main": "dist/plugin.js", "module": "dist/esm/index.js", - "types": "dist/types/index.d.ts", + "types": "dist/esm/index.d.ts", "scripts": { - "build": "npm run clean && tsc", + "build": "npm run clean && tsc && rollup -c rollup.config.js", "clean": "rimraf ./dist", "watch": "tsc --watch", "prepublishOnly": "npm run build", - "build:android": "chmod +x scripts/build-native.sh && ./scripts/build-native.sh --platform android", - "build:ios": "chmod +x scripts/build-native.sh && ./scripts/build-native.sh --platform ios", - "build:native": "chmod +x scripts/build-native.sh && ./scripts/build-native.sh --platform all", - "test:android": "cd android && ./gradlew test", - "test:ios": "cd ios && xcodebuild test -scheme DailyNotificationPlugin -destination 'platform=iOS Simulator,name=iPhone 14'", "test": "jest", - "test:watch": "jest --watch", - "test:coverage": "jest --coverage", "lint": "eslint . --ext .ts", - "lint:fix": "eslint . --ext .ts --fix", - "validate": "npm run lint && npm run build && npm run test:android", - "validate:ios": "npm run lint && npm run build && npm run test:ios", - "preinstall": "node scripts/check-environment.js", - "postinstall": "node scripts/setup-native.js" + "format": "prettier --write \"src/**/*.ts\"" }, + "keywords": [ + "capacitor", + "android", + "notification", + "daily", + "plugin" + ], "author": "Matthew Raymer", "license": "MIT", "dependencies": { - "@capacitor/android": "^5.0.0", - "@capacitor/core": "^5.0.0", - "@capacitor/ios": "^5.0.0" + "@capacitor/core": "^5.7.8" }, "devDependencies": { + "@capacitor/android": "^5.7.8", "@capacitor/cli": "^5.0.0", - "@jest/globals": "^29.5.0", - "@types/jest": "^29.5.14", - "@types/node": "^20.0.0", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", - "eslint": "^8.0.0", + "@capacitor/ios": "^5.0.0", + "@types/jest": "^29.5.0", + "@types/node": "^18.15.0", + "@typescript-eslint/eslint-plugin": "^5.57.0", + "@typescript-eslint/parser": "^5.57.0", + "eslint": "^8.37.0", "jest": "^29.5.0", - "rimraf": "^5.0.0", + "prettier": "^2.8.7", + "rimraf": "^4.4.0", + "rollup": "^3.20.0", + "rollup-plugin-typescript2": "^0.31.0", "ts-jest": "^29.1.0", "typescript": "^5.0.0" }, - "peerDependencies": { - "@capacitor/core": "^5.0.0" - }, "files": [ "dist/", "ios/", "android/", - "CapacitorDailyNotification.podspec" - ], - "keywords": [ - "capacitor", - "plugin", - "native", - "notification", - "daily" + "DailyNotificationPlugin.podspec" ], "capacitor": { "ios": { @@ -75,5 +63,17 @@ }, "bugs": { "url": "https://github.com/yourusername/capacitor-daily-notification/issues" + }, + "jest": { + "preset": "ts-jest", + "testEnvironment": "node", + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ] } } diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..9f246f7 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,25 @@ +import typescript from 'rollup-plugin-typescript2'; + +export default { + input: 'src/index.ts', + output: [ + { + file: 'dist/plugin.js', + format: 'cjs', + sourcemap: true + }, + { + file: 'dist/esm/index.js', + format: 'es', + sourcemap: true + } + ], + external: ['@capacitor/core'], + plugins: [ + typescript({ + tsconfig: './tsconfig.json', + clean: true, + useTsconfigDeclarationDir: true + }) + ] +}; \ No newline at end of file diff --git a/src/definitions.ts b/src/definitions.ts index a8d9815..648da68 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -1,98 +1,61 @@ /** - * Interface definitions for the Daily Notification plugin + * Daily Notification Plugin Definitions + * + * TypeScript definitions for the Daily Notification Plugin + * + * @author Matthew Raymer */ -export interface NotificationOptions { - url: string; - time: string; - 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; - }>; -} - -export interface NotificationStatus { - isScheduled: boolean; - nextNotificationTime?: string; - lastNotificationTime?: string; - error?: string; -} - export interface NotificationResponse { title: string; body: string; - data?: any; - timestamp: string; } -export interface NotificationSettings { - time?: string; +export interface NotificationOptions { + title?: string; + body?: string; sound?: boolean; - vibrate?: boolean; - priority?: 'low' | 'normal' | 'high'; - timezone?: string; -} - -export interface NotificationEvent extends Event { - detail: { - id: string; - action: string; - data?: any; - }; + priority?: 'high' | 'low' | 'normal'; } export interface DailyNotificationPlugin { - /** - * Schedule a daily notification with the specified options - */ - scheduleDailyNotification(options: NotificationOptions): Promise; - - /** - * Get the last notification that was delivered - */ + scheduleDailyNotification(options: NotificationOptions | ScheduleOptions): Promise; getLastNotification(): Promise; - - /** - * Cancel all scheduled notifications - */ cancelAllNotifications(): Promise; - - /** - * Get the current status of notifications - */ getNotificationStatus(): Promise; - - /** - * Update notification settings - */ updateSettings(settings: NotificationSettings): Promise; + getBatteryStatus(): Promise; + requestBatteryOptimizationExemption(): Promise; + setAdaptiveScheduling(options: { enabled: boolean }): Promise; + getPowerState(): Promise; +} - /** - * Check notification permissions - */ - checkPermissions(): Promise; +export interface ScheduleOptions { + sound?: boolean; + priority?: 'high' | 'default' | 'low' | 'min' | 'max'; + timezone?: string; +} - /** - * Request notification permissions - */ - requestPermissions(): Promise; +export interface NotificationSettings { + sound?: boolean; + priority?: string; + timezone?: string; +} + +export interface NotificationStatus { + lastNotificationTime: number; + nextNotificationTime: number; + settings: NotificationSettings; } -export interface PermissionStatus { - notifications: PermissionState; - backgroundRefresh?: PermissionState; // iOS only +export interface BatteryStatus { + level: number; + isCharging: boolean; + powerState: number; + isOptimizationExempt: boolean; } -export type PermissionState = 'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'; \ No newline at end of file +export interface PowerState { + powerState: number; + isOptimizationExempt: boolean; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index ddd1949..ffde750 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,9 +5,10 @@ import { registerPlugin } from '@capacitor/core'; import type { DailyNotificationPlugin } from './definitions'; +import { DailyNotificationWeb } from './web'; const DailyNotification = registerPlugin('DailyNotification', { - web: () => import('./web').then(m => new m.DailyNotificationWeb()), + web: async () => new DailyNotificationWeb(), }); export * from './definitions'; diff --git a/src/web.ts b/src/web.ts new file mode 100644 index 0000000..ddbe69e --- /dev/null +++ b/src/web.ts @@ -0,0 +1,63 @@ +/** + * Daily Notification Plugin Web Implementation + * + * Web implementation of the Daily Notification Plugin + * + * @author Matthew Raymer + */ + +import { DailyNotificationPlugin, NotificationOptions, NotificationResponse, NotificationSettings, NotificationStatus, BatteryStatus, PowerState, ScheduleOptions } from './definitions'; + +export class DailyNotificationWeb implements DailyNotificationPlugin { + async scheduleDailyNotification(options: NotificationOptions | ScheduleOptions): Promise { + console.warn('Daily notifications are not supported on web'); + } + + async getLastNotification(): Promise { + console.warn('Daily notifications are not supported on web'); + return null; + } + + async cancelAllNotifications(): Promise { + console.warn('Daily notifications are not supported on web'); + } + + async getNotificationStatus(): Promise { + console.warn('Daily notifications are not supported on web'); + return { + lastNotificationTime: 0, + nextNotificationTime: 0, + settings: {} + }; + } + + async updateSettings(settings: NotificationSettings): Promise { + console.warn('Daily notifications are not supported on web'); + } + + async getBatteryStatus(): Promise { + console.warn('Battery status is not supported on web'); + return { + level: 0, + isCharging: false, + powerState: 0, + isOptimizationExempt: false + }; + } + + async requestBatteryOptimizationExemption(): Promise { + console.warn('Battery optimization is not supported on web'); + } + + async setAdaptiveScheduling(options: { enabled: boolean }): Promise { + console.warn('Adaptive scheduling is not supported on web'); + } + + async getPowerState(): Promise { + console.warn('Power state is not supported on web'); + return { + powerState: 0, + isOptimizationExempt: false + }; + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index dcd50d3..096df5f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,16 +1,25 @@ { "compilerOptions": { - "target": "es2017", - "module": "commonjs", - "lib": ["es2017", "dom"], + "allowUnreachableCode": false, + "allowUnusedLabels": false, "declaration": true, - "outDir": "./dist", - "strict": true, "esModuleInterop": true, - "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "types": ["node", "jest"] + "lib": ["dom", "es2017"], + "module": "es2020", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "dist/esm", + "rootDir": "src", + "strict": true, + "target": "es2017", + "skipLibCheck": true }, - "include": ["src/**/*", "tests/**/*"], - "exclude": ["node_modules", "dist"] + "include": ["src"], + "exclude": ["node_modules", "dist", "**/*.spec.ts"] }