LCOV - code coverage report
Current view: top level - src - task_trigger.dart Coverage Total Hit
Test: lcov.info Lines: 97.2 % 107 104
Test Date: 2026-04-30 18:23:23 Functions: - 0 0

            Line data    Source code
       1              : /// Defines when a task should be executed.
       2              : sealed class TaskTrigger {
       3           12 :   const TaskTrigger();
       4              : 
       5              :   /// Convert to map for platform channel.
       6              :   Map<String, dynamic> toMap();
       7              : 
       8              :   /// Execute once after an optional delay.
       9              :   ///
      10              :   /// The most common trigger type. Schedules a task to run once, either
      11              :   /// immediately or after a specified delay.
      12              :   ///
      13              :   /// ## Immediate Execution
      14              :   ///
      15              :   /// ```dart
      16              :   /// await NativeWorkManager.enqueue(
      17              :   ///   taskId: 'immediate-task',
      18              :   ///   trigger: TaskTrigger.oneTime(),
      19              :   ///   worker: NativeWorker.httpRequest(url: 'https://api.example.com/ping'),
      20              :   /// );
      21              :   /// ```
      22              :   ///
      23              :   /// ## Delayed Execution
      24              :   ///
      25              :   /// ```dart
      26              :   /// // Execute after 5 minutes
      27              :   /// await NativeWorkManager.enqueue(
      28              :   ///   taskId: 'delayed-task',
      29              :   ///   trigger: TaskTrigger.oneTime(Duration(minutes: 5)),
      30              :   ///   worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
      31              :   /// );
      32              :   ///
      33              :   /// // Execute after 1 hour
      34              :   /// await NativeWorkManager.enqueue(
      35              :   ///   taskId: 'hourly-reminder',
      36              :   ///   trigger: TaskTrigger.oneTime(Duration(hours: 1)),
      37              :   ///   worker: DartWorker(callbackId: 'sendNotification'),
      38              :   /// );
      39              :   /// ```
      40              :   ///
      41              :   /// ## Platform Behavior
      42              :   ///
      43              :   /// **Android:**
      44              :   /// - Immediate tasks (no delay) execute as soon as constraints are met
      45              :   /// - Delayed tasks use WorkManager's initial delay
      46              :   /// - Timing is approximate (not exact)
      47              :   /// - OS may defer execution to optimize battery
      48              :   ///
      49              :   /// **iOS:**
      50              :   /// - Uses BGProcessingTask for background execution
      51              :   /// - Execution timing is opportunistic (OS decides)
      52              :   /// - May not run immediately even with zero delay
      53              :   /// - Requires app to be in background for reasonable time
      54              :   ///
      55              :   /// ## Common Pitfalls
      56              :   ///
      57              :   /// ❌ **Don't** expect exact timing (use `exact()` for that)
      58              :   /// ❌ **Don't** use for time-critical operations
      59              :   /// ✅ **Do** use for most one-time background tasks
      60              :   /// ✅ **Do** add appropriate constraints
      61              :   ///
      62              :   /// ## See Also
      63              :   ///
      64              :   /// - [periodic] - For recurring tasks
      65              :   /// - [exact] - For alarm-style exact timing
      66              :   /// - [windowed] - For execution within a time range
      67              :   const factory TaskTrigger.oneTime([Duration initialDelay]) = OneTimeTrigger;
      68              : 
      69              :   /// Execute periodically at a fixed interval.
      70              :   ///
      71              :   /// Schedules a task to run repeatedly at the specified interval. Perfect for
      72              :   /// data syncing, periodic updates, or scheduled cleanup operations.
      73              :   ///
      74              :   /// **Important:** Minimum interval is 15 minutes on Android (OS limitation).
      75              :   ///
      76              :   /// ## Basic Periodic Task
      77              :   ///
      78              :   /// ```dart
      79              :   /// // Sync every hour
      80              :   /// await NativeWorkManager.enqueue(
      81              :   ///   taskId: 'hourly-sync',
      82              :   ///   trigger: TaskTrigger.periodic(Duration(hours: 1)),
      83              :   ///   worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
      84              :   ///   constraints: Constraints.networkRequired,
      85              :   /// );
      86              :   /// ```
      87              :   ///
      88              :   /// ## Daily Cleanup
      89              :   ///
      90              :   /// ```dart
      91              :   /// // Clean up cache daily at opportune time
      92              :   /// await NativeWorkManager.enqueue(
      93              :   ///   taskId: 'daily-cleanup',
      94              :   ///   trigger: TaskTrigger.periodic(Duration(days: 1)),
      95              :   ///   worker: DartWorker(callbackId: 'cleanupCache'),
      96              :   /// );
      97              :   /// ```
      98              :   ///
      99              :   /// ## With Flex Interval (Android)
     100              :   ///
     101              :   /// ```dart
     102              :   /// // Sync every 6 hours, with 30-minute flex window
     103              :   /// // Task can run between 5.5 and 6 hours after last execution
     104              :   /// await NativeWorkManager.enqueue(
     105              :   ///   taskId: 'flexible-sync',
     106              :   ///   trigger: TaskTrigger.periodic(
     107              :   ///     Duration(hours: 6),
     108              :   ///     flexInterval: Duration(minutes: 30),
     109              :   ///   ),
     110              :   ///   worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
     111              :   /// );
     112              :   /// ```
     113              :   ///
     114              :   /// ## Minimum Interval Example
     115              :   ///
     116              :   /// ```dart
     117              :   /// // Minimum allowed interval (15 minutes)
     118              :   /// await NativeWorkManager.enqueue(
     119              :   ///   taskId: 'frequent-check',
     120              :   ///   trigger: TaskTrigger.periodic(Duration(minutes: 15)),
     121              :   ///   worker: NativeWorker.httpRequest(url: 'https://api.example.com/status'),
     122              :   /// );
     123              :   /// ```
     124              :   ///
     125              :   /// ## Periodic Task with Initial Delay
     126              :   ///
     127              :   /// ```dart
     128              :   /// // Run every hour, but wait for the first hour before starting
     129              :   /// await NativeWorkManager.enqueue(
     130              :   ///   taskId: 'delayed-periodic-sync',
     131              :   ///   trigger: TaskTrigger.periodic(
     132              :   ///     Duration(hours: 1),
     133              :   ///     initialDelay: Duration(hours: 1),
     134              :   ///   ),
     135              :   ///   worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
     136              :   /// );
     137              :   /// ```
     138              :   ///
     139              :   /// ## Skip First Run
     140              :   ///
     141              :   /// ```dart
     142              :   /// // Run every day, but don't run right now - wait for the first 24h
     143              :   /// await NativeWorkManager.enqueue(
     144              :   ///   taskId: 'daily-sync',
     145              :   ///   trigger: TaskTrigger.periodic(
     146              :   ///     Duration(days: 1),
     147              :   ///     runImmediately: false,
     148              :   ///   ),
     149              :   ///   worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
     150              :   /// );
     151              :   /// ```
     152              :   ///
     153              :   /// ## Parameters
     154              :   ///
     155              :   /// **[interval]** - Time between executions.
     156              :   /// - Must be at least 15 minutes on Android
     157              :   /// - Throws `ArgumentError` if less than 15 minutes
     158              :   /// - iOS uses this as a hint (not guaranteed)
     159              :   ///
     160              :   /// **[flexInterval]** *(optional)* - Flex time window (Android only).
     161              :   /// - Allows Android to optimize execution time within this window
     162              :   /// - Example: 6-hour interval with 30-min flex = execute between 5.5-6 hours
     163              :   /// - Improves battery life by batching work
     164              :   /// - Ignored on iOS
     165              :   ///
     166              :   /// **[initialDelay]** *(optional)* - Delay before the very first execution.
     167              :   /// - On Android, the task will only run after this delay has passed.
     168              :   /// - Useful for scheduling tasks that shouldn't run immediately upon registration.
     169              :   /// - Supported on iOS (mapped to `earliestBeginDate`).
     170              :   ///
     171              :   /// **[runImmediately]** *(optional)* - Whether to run the task immediately.
     172              :   /// - Defaults to `true`.
     173              :   /// - If `false`, the first execution will happen after one [interval] has passed.
     174              :   /// - On Android, this is natively supported in WorkManager 2.1+.
     175              :   /// - On iOS, this is simulated by setting an initial delay equal to [interval].
     176              :   ///
     177              :   /// ## Platform Behavior
     178              :   ///
     179              :   /// **Android:**
     180              :   /// - Uses WorkManager PeriodicWorkRequest
     181              :   /// - Minimum interval: 15 minutes (OS limitation)
     182              :   /// - Flex interval helps Android optimize battery usage
     183              :   /// - Initial delay allows delaying the first run (WorkManager 2.1+)
     184              :   /// - Timing is approximate, not exact
     185              :   ///
     186              :   /// **iOS:**
     187              :   /// - Uses BGAppRefreshTask
     188              :   /// - Interval is a suggestion, not guaranteed
     189              :   /// - Initial delay supported via `earliestBeginDate`
     190              :   /// - OS decides actual execution timing
     191              :   /// - May run less frequently to save battery
     192              :   ///
     193              :   /// ## When to Use
     194              :   ///
     195              :   /// ✅ **Use periodic trigger for:**
     196              :   /// - Data synchronization every N hours
     197              :   /// - Periodic content updates
     198              :   /// - Scheduled cleanup operations
     199              :   /// - Background refresh of local data
     200              :   ///
     201              :   /// ❌ **Don't use periodic for:**
     202              :   /// - Intervals < 15 minutes (will throw error)
     203              :   /// - Time-sensitive operations (use `exact` instead)
     204              :   /// - User-initiated actions (use `oneTime` instead)
     205              :   ///
     206              :   /// ## Common Pitfalls
     207              :   ///
     208              :   /// ❌ **Don't** use intervals less than 15 minutes
     209              :   /// ❌ **Don't** expect exact timing (OS optimizes for battery)
     210              :   /// ❌ **Don't** rely on precise scheduling on iOS
     211              :   /// ❌ **Don't** schedule too many periodic tasks (battery drain)
     212              :   /// ✅ **Do** use flexInterval on Android for better battery life
     213              :   /// ✅ **Do** use initialDelay if the first run shouldn't happen immediately
     214              :   /// ✅ **Do** use `runImmediately: false` to skip the first execution
     215              :   /// ✅ **Do** combine with network constraints for data sync
     216              :   ///
     217              :   /// ## Battery Impact
     218              :   ///
     219              :   /// Periodic tasks can impact battery life:
     220              :   /// - Use longest acceptable interval
     221              :   /// - Add flex interval on Android
     222              :   /// - Use appropriate constraints (WiFi, charging)
     223              :   /// - Keep task execution time short
     224              :   ///
     225              :   /// ## See Also
     226              :   ///
     227              :   /// - [oneTime] - For one-time execution
     228              :   /// - [windowed] - For execution within a time window
     229              :   /// - [Constraints] - To optimize battery usage
     230              :   const factory TaskTrigger.periodic(
     231              :     Duration interval, {
     232              :     Duration? flexInterval,
     233              :     Duration? initialDelay,
     234              :     bool runImmediately,
     235              :   }) = PeriodicTrigger;
     236              : 
     237              :   /// Execute at an exact time (alarm-style).
     238              :   ///
     239              :   /// Schedules a task to run at a specific DateTime. Unlike oneTime, this
     240              :   /// attempts to execute at the EXACT specified time, like an alarm.
     241              :   ///
     242              :   /// **⚠️ Platform Limitations:** Exact timing has significant limitations,
     243              :   /// especially on iOS. Consider if you really need exact timing before using.
     244              :   ///
     245              :   /// ## Schedule for Specific Time
     246              :   ///
     247              :   /// ```dart
     248              :   /// // Schedule for tomorrow at 9 AM
     249              :   /// final tomorrow9am = DateTime.now()
     250              :   ///     .add(Duration(days: 1))
     251              :   ///     .copyWith(hour: 9, minute: 0, second: 0);
     252              :   ///
     253              :   /// await NativeWorkManager.enqueue(
     254              :   ///   taskId: 'morning-reminder',
     255              :   ///   trigger: TaskTrigger.exact(tomorrow9am),
     256              :   ///   worker: DartWorker(callbackId: 'sendReminder'),
     257              :   /// );
     258              :   /// ```
     259              :   ///
     260              :   /// ## Schedule for 2 Hours from Now
     261              :   ///
     262              :   /// ```dart
     263              :   /// await NativeWorkManager.enqueue(
     264              :   ///   taskId: 'delayed-notification',
     265              :   ///   trigger: TaskTrigger.exact(
     266              :   ///     DateTime.now().add(Duration(hours: 2)),
     267              :   ///   ),
     268              :   ///   worker: DartWorker(callbackId: 'showNotification'),
     269              :   /// );
     270              :   /// ```
     271              :   ///
     272              :   /// ## Platform Behavior & Limitations
     273              :   ///
     274              :   /// **Android (API 31+):**
     275              :   /// - Uses AlarmManager for exact timing
     276              :   /// - **Requires SCHEDULE_EXACT_ALARM permission** (auto-granted)
     277              :   /// - User can revoke permission in Settings
     278              :   /// - Task may fail if permission revoked
     279              :   /// - Battery optimization may still defer task
     280              :   /// - Most reliable of the two platforms
     281              :   ///
     282              :   /// **iOS:**
     283              :   /// - **Severely limited - NOT recommended**
     284              :   /// - Cannot guarantee code execution at exact time
     285              :   /// - Uses UNNotification as workaround
     286              :   /// - Requires user interaction to run code
     287              :   /// - Not suitable for background tasks
     288              :   /// - Consider using `oneTime` or `windowed` instead
     289              :   ///
     290              :   /// ## When to Use
     291              :   ///
     292              :   /// ✅ **Use exact trigger for (Android only):**
     293              :   /// - Alarm clock functionality
     294              :   /// - Scheduled reminders
     295              :   /// - Time-sensitive operations
     296              :   /// - Exact appointment notifications
     297              :   ///
     298              :   /// ❌ **Don't use exact trigger for:**
     299              :   /// - iOS apps (very limited)
     300              :   /// - Background data sync (use `periodic` instead)
     301              :   /// - Flexible timing tasks (use `oneTime` or `windowed`)
     302              :   /// - Battery-sensitive operations
     303              :   ///
     304              :   /// ## Common Pitfalls
     305              :   ///
     306              :   /// ❌ **Don't** rely on this for iOS (use notifications instead)
     307              :   /// ❌ **Don't** assume permission is always granted on Android
     308              :   /// ❌ **Don't** use for routine background tasks
     309              :   /// ❌ **Don't** forget to check if scheduledTime is in future
     310              :   /// ✅ **Do** check if time is in future before scheduling
     311              :   /// ✅ **Do** handle Android permission denial gracefully
     312              :   /// ✅ **Do** consider `oneTime` or `windowed` alternatives
     313              :   /// ✅ **Do** use local notifications for UI alerts
     314              :   ///
     315              :   /// ## Alternative Solutions
     316              :   ///
     317              :   /// For most use cases, consider these alternatives:
     318              :   ///
     319              :   /// ```dart
     320              :   /// // Instead of exact alarm, use windowed:
     321              :   /// TaskTrigger.windowed(
     322              :   ///   earliest: Duration(hours: 2),
     323              :   ///   latest: Duration(hours: 2, minutes: 15),
     324              :   /// )
     325              :   ///
     326              :   /// // Or use delayed oneTime:
     327              :   /// TaskTrigger.oneTime(Duration(hours: 2))
     328              :   ///
     329              :   /// // For UI notifications, use flutter_local_notifications:
     330              :   /// await flutterLocalNotificationsPlugin.zonedSchedule(
     331              :   ///   id,
     332              :   ///   title,
     333              :   ///   body,
     334              :   ///   scheduledDateTime,
     335              :   ///   notificationDetails,
     336              :   /// );
     337              :   /// ```
     338              :   ///
     339              :   /// ## Permission Handling (Android)
     340              :   ///
     341              :   /// ```dart
     342              :   /// // Check if exact alarm permission is available
     343              :   /// if (Platform.isAndroid && Build.VERSION.SDK_INT >= 31) {
     344              :   ///   final alarmManager = AlarmManager();
     345              :   ///   if (!await alarmManager.canScheduleExactAlarms()) {
     346              :   ///     // Show dialog explaining need for permission
     347              :   ///     // Direct user to settings
     348              :   ///   }
     349              :   /// }
     350              :   /// ```
     351              :   ///
     352              :   /// ## See Also
     353              :   ///
     354              :   /// - [oneTime] - For flexible one-time execution
     355              :   /// - [windowed] - For execution within a time window
     356              :   /// - [periodic] - For recurring tasks
     357              :   const factory TaskTrigger.exact(DateTime scheduledTime) = ExactTrigger;
     358              : 
     359              :   /// Execute within a time window.
     360              :   ///
     361              :   /// Schedules a task to run sometime between two time points. More flexible
     362              :   /// than exact timing, allowing the OS to optimize battery usage by choosing
     363              :   /// the best time within the window.
     364              :   ///
     365              :   /// ## Execute Between 1-2 Hours from Now
     366              :   ///
     367              :   /// ```dart
     368              :   /// await NativeWorkManager.enqueue(
     369              :   ///   taskId: 'flexible-sync',
     370              :   ///   trigger: TaskTrigger.windowed(
     371              :   ///     earliest: Duration(hours: 1),
     372              :   ///     latest: Duration(hours: 2),
     373              :   ///   ),
     374              :   ///   worker: NativeWorker.httpSync(url: 'https://api.example.com/sync'),
     375              :   /// );
     376              :   /// ```
     377              :   ///
     378              :   /// ## Night-Time Processing
     379              :   ///
     380              :   /// ```dart
     381              :   /// // Execute sometime between 2-4 AM (low usage period)
     382              :   /// await NativeWorkManager.enqueue(
     383              :   ///   taskId: 'night-processing',
     384              :   ///   trigger: TaskTrigger.windowed(
     385              :   ///     earliest: Duration(hours: 6), // 6 hours from now
     386              :   ///     latest: Duration(hours: 8),   // 8 hours from now
     387              :   ///   ),
     388              :   ///   worker: DartWorker(callbackId: 'processLargeDataset'),
     389              :   ///   constraints: Constraints(
     390              :   ///     requiresCharging: true,
     391              :   ///     requiresWifi: true,
     392              :   ///   ),
     393              :   /// );
     394              :   /// ```
     395              :   ///
     396              :   /// ## Platform Behavior
     397              :   ///
     398              :   /// **Android:**
     399              :   /// - Uses WorkManager's OneTimeWorkRequest with flex time
     400              :   /// - OS chooses optimal execution time within window
     401              :   /// - Batches with other work for battery efficiency
     402              :   ///
     403              :   /// **iOS:**
     404              :   /// - Uses BGProcessingTask
     405              :   /// - Window is advisory (OS may defer further)
     406              :   /// - Best effort scheduling
     407              :   ///
     408              :   /// ## When to Use
     409              :   ///
     410              :   /// ✅ **Use windowed trigger for:**
     411              :   /// - Flexible data synchronization
     412              :   /// - Background processing that isn't time-critical
     413              :   /// - Heavy tasks that should run during low-usage periods
     414              :   /// - Operations that can benefit from being batched with other work
     415              :   ///
     416              :   /// ❌ **Don't use windowed for:**
     417              :   /// - Time-critical operations
     418              :   /// - User-initiated actions (use `oneTime` instead)
     419              :   ///
     420              :   /// ## See Also
     421              :   ///
     422              :   /// - [oneTime] - For simple delayed execution
     423              :   /// - [exact] - For alarm-style exact timing
     424              :   const factory TaskTrigger.windowed({
     425              :     required Duration earliest,
     426              :     required Duration latest,
     427              :   }) = WindowedTrigger;
     428              : 
     429              :   /// Execute when a content URI changes (Android only).
     430              :   ///
     431              :   /// **Android Only:** Monitors Android ContentProvider for changes and triggers
     432              :   /// task execution when detected. Perfect for reacting to media changes, contact
     433              :   /// updates, or file system modifications.
     434              :   ///
     435              :   /// **iOS:** Not supported - will return error on enqueue.
     436              :   ///
     437              :   /// ## Monitor Media Store for New Photos
     438              :   ///
     439              :   /// ```dart
     440              :   /// await NativeWorkManager.enqueue(
     441              :   ///   taskId: 'photo-backup',
     442              :   ///   trigger: TaskTrigger.contentUri(
     443              :   ///     uri: Uri.parse('content://media/external/images/media'),
     444              :   ///     triggerForDescendants: true,
     445              :   ///   ),
     446              :   ///   worker: DartWorker(callbackId: 'backupNewPhotos'),
     447              :   ///   constraints: Constraints(requiresWifi: true),
     448              :   /// );
     449              :   /// ```
     450              :   ///
     451              :   /// ## Monitor Contact Changes
     452              :   ///
     453              :   /// ```dart
     454              :   /// await NativeWorkManager.enqueue(
     455              :   ///   taskId: 'contact-sync',
     456              :   ///   trigger: TaskTrigger.contentUri(
     457              :   ///     uri: Uri.parse('content://com.android.contacts/contacts'),
     458              :   ///     triggerForDescendants: false,
     459              :   ///   ),
     460              :   ///   worker: NativeWorker.httpSync(url: 'https://api.example.com/contacts/sync'),
     461              :   /// );
     462              :   /// ```
     463              :   ///
     464              :   /// ## Common Content URIs
     465              :   ///
     466              :   /// - **Images:** `content://media/external/images/media`
     467              :   /// - **Videos:** `content://media/external/video/media`
     468              :   /// - **Audio:** `content://media/external/audio/media`
     469              :   /// - **Downloads:** `content://downloads/public_downloads`
     470              :   /// - **Contacts:** `content://com.android.contacts/contacts`
     471              :   ///
     472              :   /// ## Parameters
     473              :   ///
     474              :   /// **[triggerForDescendants]** - Monitor child URIs too.
     475              :   /// - `true`: Triggers for changes in descendant URIs
     476              :   /// - `false`: Only triggers for exact URI match
     477              :   /// - Example: With `content://media/external` and `true`, changes to
     478              :   ///   `content://media/external/images/media/123` will also trigger
     479              :   ///
     480              :   /// ## Platform Notes
     481              :   ///
     482              :   /// **Android:** Uses WorkManager ContentUriTriggers
     483              :   /// **iOS:** ❌ Not supported - task will be rejected
     484              :   ///
     485              :   /// ## When to Use
     486              :   ///
     487              :   /// ✅ **Use contentUri for (Android only):**
     488              :   /// - Auto-backup new photos/videos
     489              :   /// - React to contact changes
     490              :   /// - Monitor downloads folder
     491              :   /// - Sync when media is added
     492              :   ///
     493              :   /// ## Common Pitfalls
     494              :   ///
     495              :   /// ❌ **Don't** use on iOS (will fail)
     496              :   /// ❌ **Don't** forget `triggerForDescendants` for broad monitoring
     497              :   /// ✅ **Do** add platform check before using
     498              :   /// ✅ **Do** combine with appropriate constraints
     499              :   ///
     500              :   /// ## See Also
     501              :   ///
     502              :   /// - [oneTime] - For one-time execution
     503              :   /// - [periodic] - For time-based recurring tasks
     504              :   const factory TaskTrigger.contentUri({
     505              :     required Uri uri,
     506              :     bool triggerForDescendants,
     507              :   }) = ContentUriTrigger;
     508              : 
     509              :   /// Execute when battery is NOT low (Android only).
     510              :   ///
     511              :   /// **Android Only:** Triggers when battery level is above the "low" threshold
     512              :   /// (typically above 15%). Useful for battery-friendly operations.
     513              :   ///
     514              :   /// **iOS:** Not supported - returns REJECTED_OS_POLICY.
     515              :   ///
     516              :   /// ```dart
     517              :   /// // Schedule backup that only runs when battery is okay
     518              :   /// await NativeWorkManager.enqueue(
     519              :   ///   taskId: 'safe-backup',
     520              :   ///   trigger: TaskTrigger.batteryOkay(),
     521              :   ///   worker: NativeWorker.httpUpload(
     522              :   ///     url: 'https://api.example.com/backup',
     523              :   ///     filePath: '/data/backup.zip',
     524              :   ///   ),
     525              :   /// );
     526              :   /// ```
     527              :   ///
     528              :   /// **Note:** Consider using `Constraints(requiresBatteryNotLow: true)` with
     529              :   /// `oneTime` trigger instead for more control.
     530              :   const factory TaskTrigger.batteryOkay() = BatteryOkayTrigger;
     531              : 
     532              :   /// Execute when battery IS low (Android only).
     533              :   ///
     534              :   /// **Android Only:** Triggers when battery drops below 15%. Useful for warning
     535              :   /// users or reducing background activity.
     536              :   ///
     537              :   /// **iOS:** Not supported - returns REJECTED_OS_POLICY.
     538              :   ///
     539              :   /// ```dart
     540              :   /// // Notify user to charge device
     541              :   /// await NativeWorkManager.enqueue(
     542              :   ///   taskId: 'low-battery-warning',
     543              :   ///   trigger: TaskTrigger.batteryLow(),
     544              :   ///   worker: DartWorker(callbackId: 'showLowBatteryNotification'),
     545              :   /// );
     546              :   /// ```
     547              :   const factory TaskTrigger.batteryLow() = BatteryLowTrigger;
     548              : 
     549              :   /// Execute when device is idle (Android only).
     550              :   ///
     551              :   /// **Android Only:** Triggers when device enters idle/Doze mode (screen off,
     552              :   /// stationary, not charging). Perfect for maintenance tasks.
     553              :   ///
     554              :   /// **iOS:** Not supported - returns REJECTED_OS_POLICY.
     555              :   ///
     556              :   /// ```dart
     557              :   /// // Database maintenance during idle time
     558              :   /// await NativeWorkManager.enqueue(
     559              :   ///   taskId: 'db-maintenance',
     560              :   ///   trigger: TaskTrigger.deviceIdle(),
     561              :   ///   worker: DartWorker(callbackId: 'optimizeDatabase'),
     562              :   /// );
     563              :   /// ```
     564              :   ///
     565              :   /// **Use case:** Database vacuuming, cache cleanup, index optimization.
     566              :   const factory TaskTrigger.deviceIdle() = DeviceIdleTrigger;
     567              : 
     568              :   /// Execute when storage is low (Android only).
     569              :   ///
     570              :   /// **Android Only:** Triggers when device storage drops below threshold.
     571              :   /// Useful for cleanup operations.
     572              :   ///
     573              :   /// **iOS:** Not supported - returns REJECTED_OS_POLICY.
     574              :   ///
     575              :   /// ```dart
     576              :   /// // Auto-cleanup when storage is low
     577              :   /// await NativeWorkManager.enqueue(
     578              :   ///   taskId: 'emergency-cleanup',
     579              :   ///   trigger: TaskTrigger.storageLow(),
     580              :   ///   worker: DartWorker(callbackId: 'deleteOldCache'),
     581              :   /// );
     582              :   /// ```
     583              :   ///
     584              :   /// **Use case:** Delete old files, clear caches, compress data.
     585              :   const factory TaskTrigger.storageLow() = StorageLowTrigger;
     586              : }
     587              : 
     588              : /// Execute once after an optional delay.
     589              : class OneTimeTrigger extends TaskTrigger {
     590            9 :   const OneTimeTrigger([this.initialDelay = Duration.zero]);
     591              : 
     592              :   /// Delay before execution.
     593              :   final Duration initialDelay;
     594              : 
     595            2 :   @override
     596            2 :   Map<String, dynamic> toMap() => {
     597              :         'type': 'oneTime',
     598            4 :         'initialDelayMs': initialDelay.inMilliseconds,
     599              :       };
     600              : 
     601            1 :   @override
     602              :   bool operator ==(Object other) =>
     603              :       identical(this, other) ||
     604            4 :       other is OneTimeTrigger && initialDelay == other.initialDelay;
     605              : 
     606            1 :   @override
     607            2 :   int get hashCode => initialDelay.hashCode;
     608              : 
     609            1 :   @override
     610            2 :   String toString() => 'TaskTrigger.oneTime($initialDelay)';
     611              : }
     612              : 
     613              : /// Execute periodically at a fixed interval.
     614              : class PeriodicTrigger extends TaskTrigger {
     615            4 :   const PeriodicTrigger(
     616              :     this.interval, {
     617              :     this.flexInterval,
     618              :     this.initialDelay,
     619              :     this.runImmediately = true,
     620              :   });
     621              : 
     622              :   /// Interval between executions.
     623              :   final Duration interval;
     624              : 
     625              :   /// Flex time window for execution.
     626              :   final Duration? flexInterval;
     627              : 
     628              :   /// Initial delay before first execution.
     629              :   final Duration? initialDelay;
     630              : 
     631              :   /// Whether to run the task immediately.
     632              :   final bool runImmediately;
     633              : 
     634              :   static const Duration _androidMinInterval = Duration(minutes: 15);
     635              :   static const Duration _androidMinFlex = Duration(minutes: 5);
     636              : 
     637            3 :   @override
     638              :   Map<String, dynamic> toMap() {
     639            6 :     if (interval < _androidMinInterval) {
     640            1 :       throw ArgumentError.value(
     641            1 :         interval,
     642            1 :         'interval',
     643              :         'Periodic interval must be at least 15 minutes on Android. '
     644            2 :             'Received ${interval.inMinutes} min. '
     645              :             'Android WorkManager silently clamps values below 15 min, masking bugs. '
     646              :             'Use Duration(minutes: 15) or longer.',
     647              :       );
     648              :     }
     649            3 :     final flex = flexInterval;
     650            1 :     if (flex != null && flex < _androidMinFlex) {
     651            0 :       throw ArgumentError.value(
     652              :         flex,
     653            0 :         'flexInterval',
     654              :         'flexInterval must be at least 5 minutes on Android. '
     655            0 :             'Received ${flex.inMinutes} min. '
     656              :             'WorkManager rejects values below 5 min with IllegalArgumentException at runtime.',
     657              :       );
     658              :     }
     659              :     assert(
     660            3 :       runImmediately || (initialDelay?.inMilliseconds ?? 0) == 0,
     661              :       'runImmediately: false and initialDelay cannot both be set — behaviour is undefined. '
     662              :       'Use one or the other: initialDelay to specify an exact first-run offset, '
     663              :       'or runImmediately: false to defer by one full interval.',
     664              :     );
     665            3 :     return {
     666              :       'type': 'periodic',
     667            6 :       'intervalMs': interval.inMilliseconds,
     668            4 :       'flexMs': flexInterval?.inMilliseconds,
     669            5 :       'initialDelayMs': initialDelay?.inMilliseconds,
     670            3 :       'runImmediately': runImmediately,
     671              :     };
     672              :   }
     673              : 
     674            1 :   @override
     675              :   bool operator ==(Object other) =>
     676              :       identical(this, other) ||
     677            1 :       other is PeriodicTrigger &&
     678            3 :           interval == other.interval &&
     679            3 :           flexInterval == other.flexInterval &&
     680            3 :           initialDelay == other.initialDelay &&
     681            3 :           runImmediately == other.runImmediately;
     682              : 
     683            1 :   @override
     684              :   int get hashCode =>
     685            5 :       Object.hash(interval, flexInterval, initialDelay, runImmediately);
     686              : 
     687            1 :   @override
     688              :   String toString() =>
     689            5 :       'TaskTrigger.periodic($interval, flex: $flexInterval, initialDelay: $initialDelay, runImmediately: $runImmediately)';
     690              : }
     691              : 
     692              : /// Execute at an exact time.
     693              : class ExactTrigger extends TaskTrigger {
     694            3 :   const ExactTrigger(this.scheduledTime);
     695              : 
     696              :   /// The exact time to execute.
     697              :   final DateTime scheduledTime;
     698              : 
     699            2 :   @override
     700            2 :   Map<String, dynamic> toMap() => {
     701              :         'type': 'exact',
     702            4 :         'scheduledTimeMs': scheduledTime.millisecondsSinceEpoch,
     703              :       };
     704              : 
     705            1 :   @override
     706              :   bool operator ==(Object other) =>
     707              :       identical(this, other) ||
     708            4 :       other is ExactTrigger && scheduledTime == other.scheduledTime;
     709              : 
     710            1 :   @override
     711            2 :   int get hashCode => scheduledTime.hashCode;
     712              : 
     713            1 :   @override
     714            2 :   String toString() => 'TaskTrigger.exact($scheduledTime)';
     715              : }
     716              : 
     717              : /// Execute within a time window.
     718              : class WindowedTrigger extends TaskTrigger {
     719            2 :   const WindowedTrigger({
     720              :     required this.earliest,
     721              :     required this.latest,
     722              :   });
     723              : 
     724              :   /// Earliest time to start.
     725              :   final Duration earliest;
     726              : 
     727              :   /// Latest time to complete.
     728              :   final Duration latest;
     729              : 
     730            2 :   @override
     731            2 :   Map<String, dynamic> toMap() => {
     732              :         'type': 'windowed',
     733            4 :         'earliestMs': earliest.inMilliseconds,
     734            4 :         'latestMs': latest.inMilliseconds,
     735              :       };
     736              : 
     737            1 :   @override
     738              :   bool operator ==(Object other) =>
     739              :       identical(this, other) ||
     740            1 :       other is WindowedTrigger &&
     741            3 :           earliest == other.earliest &&
     742            3 :           latest == other.latest;
     743              : 
     744            1 :   @override
     745            3 :   int get hashCode => Object.hash(earliest, latest);
     746              : 
     747            1 :   @override
     748            3 :   String toString() => 'TaskTrigger.windowed($earliest - $latest)';
     749              : }
     750              : 
     751              : /// Execute when a content URI changes (Android only).
     752              : class ContentUriTrigger extends TaskTrigger {
     753            3 :   const ContentUriTrigger({
     754              :     required this.uri,
     755              :     this.triggerForDescendants = false,
     756              :   });
     757              : 
     758              :   /// Content URI to observe.
     759              :   ///
     760              :   /// Common examples:
     761              :   /// - `Uri.parse('content://media/external/images/media')` - MediaStore images
     762              :   /// - `Uri.parse('content://media/external/video/media')` - MediaStore videos
     763              :   /// - `Uri.parse('content://com.android.contacts/contacts')` - Contacts
     764              :   final Uri uri;
     765              : 
     766              :   /// If true, triggers for changes in descendant URIs as well.
     767              :   ///
     768              :   /// Example: If uri is `content://media/external` and this is true,
     769              :   /// changes to `content://media/external/images/media/123` will also trigger.
     770              :   final bool triggerForDescendants;
     771              : 
     772            3 :   @override
     773            3 :   Map<String, dynamic> toMap() => {
     774              :         'type': 'contentUri',
     775            6 :         'uriString': uri.toString(),
     776            3 :         'triggerForDescendants': triggerForDescendants,
     777              :       };
     778              : 
     779            1 :   @override
     780              :   bool operator ==(Object other) =>
     781              :       identical(this, other) ||
     782            1 :       other is ContentUriTrigger &&
     783            3 :           uri == other.uri &&
     784            3 :           triggerForDescendants == other.triggerForDescendants;
     785              : 
     786            1 :   @override
     787            3 :   int get hashCode => Object.hash(uri, triggerForDescendants);
     788              : 
     789            1 :   @override
     790              :   String toString() =>
     791            3 :       'TaskTrigger.contentUri($uri, descendants: $triggerForDescendants)';
     792              : }
     793              : 
     794              : /// Execute when battery is NOT low (Android only).
     795              : class BatteryOkayTrigger extends TaskTrigger {
     796            1 :   const BatteryOkayTrigger();
     797              : 
     798            1 :   @override
     799            1 :   Map<String, dynamic> toMap() => {
     800              :         'type': 'batteryOkay',
     801              :       };
     802              : 
     803            1 :   @override
     804              :   bool operator ==(Object other) =>
     805            1 :       identical(this, other) || other is BatteryOkayTrigger;
     806              : 
     807            1 :   @override
     808            1 :   int get hashCode => 'batteryOkay'.hashCode;
     809              : 
     810            1 :   @override
     811              :   String toString() => 'TaskTrigger.batteryOkay()';
     812              : }
     813              : 
     814              : /// Execute when battery IS low (Android only).
     815              : class BatteryLowTrigger extends TaskTrigger {
     816            1 :   const BatteryLowTrigger();
     817              : 
     818            1 :   @override
     819            1 :   Map<String, dynamic> toMap() => {
     820              :         'type': 'batteryLow',
     821              :       };
     822              : 
     823            1 :   @override
     824              :   bool operator ==(Object other) =>
     825            1 :       identical(this, other) || other is BatteryLowTrigger;
     826              : 
     827            1 :   @override
     828            1 :   int get hashCode => 'batteryLow'.hashCode;
     829              : 
     830            1 :   @override
     831              :   String toString() => 'TaskTrigger.batteryLow()';
     832              : }
     833              : 
     834              : /// Execute when device is idle (Android only).
     835              : class DeviceIdleTrigger extends TaskTrigger {
     836            1 :   const DeviceIdleTrigger();
     837              : 
     838            1 :   @override
     839            1 :   Map<String, dynamic> toMap() => {
     840              :         'type': 'deviceIdle',
     841              :       };
     842              : 
     843            1 :   @override
     844              :   bool operator ==(Object other) =>
     845            1 :       identical(this, other) || other is DeviceIdleTrigger;
     846              : 
     847            1 :   @override
     848            1 :   int get hashCode => 'deviceIdle'.hashCode;
     849              : 
     850            1 :   @override
     851              :   String toString() => 'TaskTrigger.deviceIdle()';
     852              : }
     853              : 
     854              : /// Execute when storage is low (Android only).
     855              : class StorageLowTrigger extends TaskTrigger {
     856            1 :   const StorageLowTrigger();
     857              : 
     858            1 :   @override
     859            1 :   Map<String, dynamic> toMap() => {
     860              :         'type': 'storageLow',
     861              :       };
     862              : 
     863            1 :   @override
     864              :   bool operator ==(Object other) =>
     865            1 :       identical(this, other) || other is StorageLowTrigger;
     866              : 
     867            1 :   @override
     868            1 :   int get hashCode => 'storageLow'.hashCode;
     869              : 
     870            1 :   @override
     871              :   String toString() => 'TaskTrigger.storageLow()';
     872              : }
        

Generated by: LCOV version 2.4-0