refactor(android): P2.1 Batch C - complete glue & orchestration delegation
- Refactor updateStarredPlans() to delegate to ScheduleHelper - Refactor getSchedulesWithStatus() to delegate to ScheduleHelper - Refactor scheduleUserNotification() to delegate to ScheduleHelper - Refactor scheduleDailyNotification() to delegate to ScheduleHelper (largest refactor) - Refactor scheduleDualNotification() to delegate to ScheduleHelper - Document configure() for future TimeSafariIntegrationManager integration Adds 5 helper methods to ScheduleHelper for orchestration logic. Reduces plugin class by ~200+ lines of orchestration code. Batch C complete: 6 methods refactored. Total P2.1 progress: 28 methods. Refs: docs/progress/P2.1-BATCH-C-STATE.md
This commit is contained in:
@@ -209,10 +209,13 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
val options = call.data
|
||||
Log.i(TAG, "Configure called with options: $options")
|
||||
|
||||
// Store configuration in database
|
||||
// Delegate to TimeSafariIntegrationManager if available
|
||||
// For now, this is a placeholder - configuration will be handled by integration manager
|
||||
// when it's initialized. This method maintains API compatibility.
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
// Implementation would store config in database
|
||||
// TODO: Initialize TimeSafariIntegrationManager and delegate configure()
|
||||
// For now, just resolve to maintain API compatibility
|
||||
call.resolve()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to configure", e)
|
||||
@@ -378,39 +381,20 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
return call.reject("planIds must be a string array, got: ${planIdsValue.javaClass.simpleName}")
|
||||
}
|
||||
|
||||
// Validate all plan IDs are non-empty strings
|
||||
planIds.forEachIndexed { index, planId ->
|
||||
if (planId.isBlank()) {
|
||||
return call.reject("planIds[$index] must be a non-empty string")
|
||||
// Delegate to ScheduleHelper
|
||||
val success = ScheduleHelper.updateStarredPlans(context, planIds)
|
||||
|
||||
if (success) {
|
||||
val result = JSObject().apply {
|
||||
put("success", true)
|
||||
put("planIdsCount", planIds.size)
|
||||
put("updatedAt", System.currentTimeMillis())
|
||||
}
|
||||
call.resolve(result)
|
||||
} else {
|
||||
call.reject("Failed to update starred plans")
|
||||
}
|
||||
|
||||
Log.i(TAG, "Updating starred plans: count=${planIds.size}")
|
||||
|
||||
// Store in SharedPreferences (matching TestNativeFetcher expectations)
|
||||
val prefsName = DailyNotificationConstants.PREFS_NAME
|
||||
val keyStarredPlanIds = DailyNotificationConstants.PREFS_KEY_STARRED_PLAN_IDS
|
||||
|
||||
val prefs: SharedPreferences = context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
|
||||
val editor = prefs.edit()
|
||||
|
||||
// Convert planIds list to JSON array string
|
||||
val jsonArray = JSONArray()
|
||||
planIds.forEach { planId ->
|
||||
jsonArray.put(planId)
|
||||
}
|
||||
editor.putString(keyStarredPlanIds, jsonArray.toString())
|
||||
editor.apply()
|
||||
|
||||
val result = JSObject().apply {
|
||||
put("success", true)
|
||||
put("planIdsCount", planIds.size)
|
||||
put("updatedAt", System.currentTimeMillis())
|
||||
}
|
||||
|
||||
Log.i(TAG, "Starred plans updated: count=${planIds.size}")
|
||||
call.resolve(result)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to update starred plans", e)
|
||||
call.reject("Failed to update starred plans: ${e.message}")
|
||||
@@ -1048,7 +1032,7 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
val body = options.getString("body") ?: ""
|
||||
val sound = options.getBoolean("sound") ?: true
|
||||
val priority = options.getString("priority") ?: "default"
|
||||
val url = options.getString("url") // Optional URL for prefetch
|
||||
val url = options.getString("url") // Optional URL for prefetch (not used in helper yet)
|
||||
|
||||
Log.i(TAG, "Scheduling daily notification: time=$time, title=$title")
|
||||
|
||||
@@ -1087,74 +1071,21 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
priority = priority
|
||||
)
|
||||
|
||||
val nextRunTime = calculateNextRunTime(cronExpression)
|
||||
|
||||
// Schedule AlarmManager notification as static reminder
|
||||
// (doesn't require cached content)
|
||||
NotifyReceiver.scheduleExactNotification(
|
||||
context,
|
||||
nextRunTime,
|
||||
// Delegate to ScheduleHelper
|
||||
val success = ScheduleHelper.scheduleDailyNotification(
|
||||
context,
|
||||
getDatabase(),
|
||||
scheduleId,
|
||||
config,
|
||||
isStaticReminder = true,
|
||||
reminderId = scheduleId,
|
||||
scheduleId = scheduleId,
|
||||
source = ScheduleSource.INITIAL_SETUP
|
||||
time,
|
||||
::calculateNextRunTime
|
||||
)
|
||||
|
||||
// Always schedule prefetch 2 minutes before notification
|
||||
// (URL is optional - native fetcher will be used if registered)
|
||||
val fetchTime = nextRunTime - (2 * 60 * 1000L) // 2 minutes before
|
||||
val delayMs = fetchTime - System.currentTimeMillis()
|
||||
|
||||
if (delayMs > 0) {
|
||||
// Schedule delayed prefetch
|
||||
val inputData = Data.Builder()
|
||||
.putLong("scheduled_time", nextRunTime)
|
||||
.putLong("fetch_time", fetchTime)
|
||||
.putInt("retry_count", 0)
|
||||
.putBoolean("immediate", false)
|
||||
.build()
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<DailyNotificationFetchWorker>()
|
||||
.setInitialDelay(delayMs, TimeUnit.MILLISECONDS)
|
||||
.setInputData(inputData)
|
||||
.addTag("prefetch")
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueue(workRequest)
|
||||
|
||||
Log.i(TAG, "Prefetch scheduled: fetchTime=$fetchTime, notificationTime=$nextRunTime, delayMs=$delayMs, using native fetcher")
|
||||
if (success) {
|
||||
call.resolve()
|
||||
} else {
|
||||
// Fetch time is in the past, schedule immediate fetch
|
||||
val inputData = Data.Builder()
|
||||
.putLong("scheduled_time", nextRunTime)
|
||||
.putLong("fetch_time", System.currentTimeMillis())
|
||||
.putInt("retry_count", 0)
|
||||
.putBoolean("immediate", true)
|
||||
.build()
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<DailyNotificationFetchWorker>()
|
||||
.setInputData(inputData)
|
||||
.addTag("prefetch")
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueue(workRequest)
|
||||
|
||||
Log.i(TAG, "Immediate prefetch scheduled: notificationTime=$nextRunTime, using native fetcher")
|
||||
call.reject("Daily notification scheduling failed")
|
||||
}
|
||||
|
||||
// Store schedule in database
|
||||
val schedule = Schedule(
|
||||
id = scheduleId,
|
||||
kind = "notify",
|
||||
cron = cronExpression,
|
||||
clockTime = time,
|
||||
enabled = true,
|
||||
nextRunAt = nextRunTime
|
||||
)
|
||||
getDatabase().scheduleDao().upsert(schedule)
|
||||
|
||||
call.resolve()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to schedule daily notification", e)
|
||||
call.reject("Daily notification scheduling failed: ${e.message}")
|
||||
@@ -1358,31 +1289,19 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val nextRunTime = calculateNextRunTime(config.schedule)
|
||||
|
||||
// Generate scheduleId before scheduling (needed for stable requestCode)
|
||||
val scheduleId = "notify_${System.currentTimeMillis()}"
|
||||
|
||||
// Schedule AlarmManager notification
|
||||
NotifyReceiver.scheduleExactNotification(
|
||||
context,
|
||||
nextRunTime,
|
||||
// Delegate to ScheduleHelper
|
||||
val scheduleId = ScheduleHelper.scheduleUserNotification(
|
||||
context,
|
||||
getDatabase(),
|
||||
config,
|
||||
scheduleId = scheduleId,
|
||||
source = ScheduleSource.INITIAL_SETUP
|
||||
::calculateNextRunTime
|
||||
)
|
||||
|
||||
// Store schedule in database using ScheduleHelper
|
||||
val schedule = Schedule(
|
||||
id = scheduleId,
|
||||
kind = "notify",
|
||||
cron = config.schedule,
|
||||
enabled = config.enabled,
|
||||
nextRunAt = nextRunTime
|
||||
)
|
||||
ScheduleHelper.createSchedule(getDatabase(), schedule)
|
||||
|
||||
call.resolve()
|
||||
if (scheduleId != null) {
|
||||
call.resolve()
|
||||
} else {
|
||||
call.reject("User notification scheduling failed")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to schedule user notification", e)
|
||||
call.reject("User notification scheduling failed: ${e.message}")
|
||||
@@ -1423,39 +1342,21 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
// Schedule both fetch and notification
|
||||
FetchWorker.scheduleFetch(context, contentFetchConfig)
|
||||
|
||||
val nextRunTime = calculateNextRunTime(userNotificationConfig.schedule)
|
||||
val scheduleId = "notify_${System.currentTimeMillis()}"
|
||||
NotifyReceiver.scheduleExactNotification(
|
||||
context,
|
||||
nextRunTime,
|
||||
// Delegate to ScheduleHelper
|
||||
val success = ScheduleHelper.scheduleDualNotification(
|
||||
context,
|
||||
getDatabase(),
|
||||
contentFetchConfig,
|
||||
userNotificationConfig,
|
||||
scheduleId = scheduleId,
|
||||
source = ScheduleSource.INITIAL_SETUP
|
||||
FetchWorker::scheduleFetch,
|
||||
::calculateNextRunTime
|
||||
)
|
||||
|
||||
// Store both schedules
|
||||
val fetchSchedule = Schedule(
|
||||
id = "dual_fetch_${System.currentTimeMillis()}",
|
||||
kind = "fetch",
|
||||
cron = contentFetchConfig.schedule,
|
||||
enabled = contentFetchConfig.enabled,
|
||||
nextRunAt = calculateNextRunTime(contentFetchConfig.schedule)
|
||||
)
|
||||
val notifySchedule = Schedule(
|
||||
id = "dual_notify_${System.currentTimeMillis()}",
|
||||
kind = "notify",
|
||||
cron = userNotificationConfig.schedule,
|
||||
enabled = userNotificationConfig.enabled,
|
||||
nextRunAt = nextRunTime
|
||||
)
|
||||
|
||||
getDatabase().scheduleDao().upsert(fetchSchedule)
|
||||
getDatabase().scheduleDao().upsert(notifySchedule)
|
||||
|
||||
call.resolve()
|
||||
if (success) {
|
||||
call.resolve()
|
||||
} else {
|
||||
call.reject("Dual notification scheduling failed")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to schedule dual notification", e)
|
||||
call.reject("Dual notification scheduling failed: ${e.message}")
|
||||
@@ -1630,6 +1531,7 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
|
||||
val context = context ?: return@launch call.reject("Context not available")
|
||||
|
||||
// Get schedules from database (same logic as getSchedules())
|
||||
val schedules = when {
|
||||
kind != null && enabled != null ->
|
||||
getDatabase().scheduleDao().getByKindAndEnabled(kind, enabled)
|
||||
@@ -1641,20 +1543,9 @@ open class DailyNotificationPlugin : Plugin() {
|
||||
getDatabase().scheduleDao().getAll()
|
||||
}
|
||||
|
||||
// For each schedule, check if it's actually scheduled in AlarmManager
|
||||
val schedulesArray = org.json.JSONArray()
|
||||
schedules.forEach { schedule ->
|
||||
val scheduleJson = scheduleToJson(schedule)
|
||||
|
||||
// Only check AlarmManager status for "notify" schedules with nextRunAt
|
||||
if (schedule.kind == "notify" && schedule.nextRunAt != null) {
|
||||
val isScheduled = NotifyReceiver.isAlarmScheduled(context, scheduleId = schedule.id, triggerAtMillis = schedule.nextRunAt!!)
|
||||
scheduleJson.put("isActuallyScheduled", isScheduled)
|
||||
} else {
|
||||
scheduleJson.put("isActuallyScheduled", false)
|
||||
}
|
||||
|
||||
schedulesArray.put(scheduleJson)
|
||||
// Delegate to ScheduleHelper to combine with AlarmManager status
|
||||
val schedulesArray = ScheduleHelper.getSchedulesWithStatus(context, schedules) { schedule ->
|
||||
scheduleToJson(schedule)
|
||||
}
|
||||
|
||||
call.resolve(JSObject().apply {
|
||||
@@ -2554,6 +2445,45 @@ object TestDataHelper {
|
||||
* Provides functions for managing schedules in the database
|
||||
*/
|
||||
object ScheduleHelper {
|
||||
/**
|
||||
* Update starred plan IDs in SharedPreferences
|
||||
*
|
||||
* @param context Application context
|
||||
* @param planIds List of plan IDs to star
|
||||
* @return true if update was successful
|
||||
*/
|
||||
fun updateStarredPlans(context: Context, planIds: List<String>): Boolean {
|
||||
return try {
|
||||
// Validate all plan IDs are non-empty strings
|
||||
planIds.forEachIndexed { index, planId ->
|
||||
if (planId.isBlank()) {
|
||||
throw IllegalArgumentException("planIds[$index] must be a non-empty string")
|
||||
}
|
||||
}
|
||||
|
||||
// Store in SharedPreferences (matching TestNativeFetcher expectations)
|
||||
val prefsName = DailyNotificationConstants.PREFS_NAME
|
||||
val keyStarredPlanIds = DailyNotificationConstants.PREFS_KEY_STARRED_PLAN_IDS
|
||||
|
||||
val prefs = context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
|
||||
val editor = prefs.edit()
|
||||
|
||||
// Convert planIds list to JSON array string
|
||||
val jsonArray = JSONArray()
|
||||
planIds.forEach { planId ->
|
||||
jsonArray.put(planId)
|
||||
}
|
||||
editor.putString(keyStarredPlanIds, jsonArray.toString())
|
||||
editor.apply()
|
||||
|
||||
Log.i("ScheduleHelper", "Starred plans updated: count=${planIds.size}")
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Log.e("ScheduleHelper", "Failed to update starred plans", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable all schedules of a specific kind
|
||||
*
|
||||
@@ -2602,6 +2532,240 @@ object ScheduleHelper {
|
||||
return cancelledCount
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schedules with AlarmManager status
|
||||
*
|
||||
* Combines database schedules with AlarmManager status checks.
|
||||
*
|
||||
* @param context Application context
|
||||
* @param schedules List of schedules from database
|
||||
* @return JSONArray of schedules with isActuallyScheduled field added
|
||||
*/
|
||||
fun getSchedulesWithStatus(context: Context, schedules: List<Schedule>, scheduleToJson: (Schedule) -> org.json.JSONObject): org.json.JSONArray {
|
||||
val schedulesArray = org.json.JSONArray()
|
||||
schedules.forEach { schedule ->
|
||||
val scheduleJson = scheduleToJson(schedule)
|
||||
|
||||
// Only check AlarmManager status for "notify" schedules with nextRunAt
|
||||
if (schedule.kind == "notify" && schedule.nextRunAt != null) {
|
||||
val isScheduled = NotifyReceiver.isAlarmScheduled(context, scheduleId = schedule.id, triggerAtMillis = schedule.nextRunAt!!)
|
||||
scheduleJson.put("isActuallyScheduled", isScheduled)
|
||||
} else {
|
||||
scheduleJson.put("isActuallyScheduled", false)
|
||||
}
|
||||
|
||||
schedulesArray.put(scheduleJson)
|
||||
}
|
||||
return schedulesArray
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule daily notification (alarm + prefetch + database)
|
||||
*
|
||||
* Orchestrates scheduling a daily notification with prefetch WorkManager job.
|
||||
*
|
||||
* @param context Application context
|
||||
* @param database Database instance
|
||||
* @param scheduleId Schedule ID (stable for "one per day" semantics)
|
||||
* @param config User notification configuration
|
||||
* @param clockTime Original HH:mm time string
|
||||
* @param calculateNextRunTime Function to calculate next run time from cron expression
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
suspend fun scheduleDailyNotification(
|
||||
context: Context,
|
||||
database: DailyNotificationDatabase,
|
||||
scheduleId: String,
|
||||
config: UserNotificationConfig,
|
||||
clockTime: String,
|
||||
calculateNextRunTime: (String) -> Long
|
||||
): Boolean {
|
||||
return try {
|
||||
val nextRunTime = calculateNextRunTime(config.schedule)
|
||||
|
||||
// Schedule AlarmManager notification as static reminder
|
||||
// (doesn't require cached content)
|
||||
NotifyReceiver.scheduleExactNotification(
|
||||
context,
|
||||
nextRunTime,
|
||||
config,
|
||||
isStaticReminder = true,
|
||||
reminderId = scheduleId,
|
||||
scheduleId = scheduleId,
|
||||
source = ScheduleSource.INITIAL_SETUP
|
||||
)
|
||||
|
||||
// Always schedule prefetch 2 minutes before notification
|
||||
// (URL is optional - native fetcher will be used if registered)
|
||||
val fetchTime = nextRunTime - (2 * 60 * 1000L) // 2 minutes before
|
||||
val delayMs = fetchTime - System.currentTimeMillis()
|
||||
|
||||
if (delayMs > 0) {
|
||||
// Schedule delayed prefetch
|
||||
val inputData = Data.Builder()
|
||||
.putLong("scheduled_time", nextRunTime)
|
||||
.putLong("fetch_time", fetchTime)
|
||||
.putInt("retry_count", 0)
|
||||
.putBoolean("immediate", false)
|
||||
.build()
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<DailyNotificationFetchWorker>()
|
||||
.setInitialDelay(delayMs, TimeUnit.MILLISECONDS)
|
||||
.setInputData(inputData)
|
||||
.addTag("prefetch")
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueue(workRequest)
|
||||
|
||||
Log.i("ScheduleHelper", "Prefetch scheduled: fetchTime=$fetchTime, notificationTime=$nextRunTime, delayMs=$delayMs")
|
||||
} else {
|
||||
// Fetch time is in the past, schedule immediate fetch
|
||||
val inputData = Data.Builder()
|
||||
.putLong("scheduled_time", nextRunTime)
|
||||
.putLong("fetch_time", System.currentTimeMillis())
|
||||
.putInt("retry_count", 0)
|
||||
.putBoolean("immediate", true)
|
||||
.build()
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<DailyNotificationFetchWorker>()
|
||||
.setInputData(inputData)
|
||||
.addTag("prefetch")
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueue(workRequest)
|
||||
|
||||
Log.i("ScheduleHelper", "Immediate prefetch scheduled: notificationTime=$nextRunTime")
|
||||
}
|
||||
|
||||
// Store schedule in database
|
||||
val schedule = Schedule(
|
||||
id = scheduleId,
|
||||
kind = "notify",
|
||||
cron = config.schedule,
|
||||
clockTime = clockTime,
|
||||
enabled = true,
|
||||
nextRunAt = nextRunTime
|
||||
)
|
||||
database.scheduleDao().upsert(schedule)
|
||||
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Log.e("ScheduleHelper", "Failed to schedule daily notification", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule dual notification (fetch + notify)
|
||||
*
|
||||
* Orchestrates scheduling both content fetch and user notification.
|
||||
*
|
||||
* @param context Application context
|
||||
* @param database Database instance
|
||||
* @param contentFetchConfig Content fetch configuration
|
||||
* @param userNotificationConfig User notification configuration
|
||||
* @param scheduleFetch Function to schedule fetch (FetchWorker.scheduleFetch)
|
||||
* @param calculateNextRunTime Function to calculate next run time from cron expression
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
suspend fun scheduleDualNotification(
|
||||
context: Context,
|
||||
database: DailyNotificationDatabase,
|
||||
contentFetchConfig: ContentFetchConfig,
|
||||
userNotificationConfig: UserNotificationConfig,
|
||||
scheduleFetch: (Context, ContentFetchConfig) -> Unit,
|
||||
calculateNextRunTime: (String) -> Long
|
||||
): Boolean {
|
||||
return try {
|
||||
// Schedule fetch
|
||||
scheduleFetch(context, contentFetchConfig)
|
||||
|
||||
// Schedule notification
|
||||
val nextRunTime = calculateNextRunTime(userNotificationConfig.schedule)
|
||||
val scheduleId = "notify_${System.currentTimeMillis()}"
|
||||
NotifyReceiver.scheduleExactNotification(
|
||||
context,
|
||||
nextRunTime,
|
||||
userNotificationConfig,
|
||||
scheduleId = scheduleId,
|
||||
source = ScheduleSource.INITIAL_SETUP
|
||||
)
|
||||
|
||||
// Store both schedules
|
||||
val fetchSchedule = Schedule(
|
||||
id = "dual_fetch_${System.currentTimeMillis()}",
|
||||
kind = "fetch",
|
||||
cron = contentFetchConfig.schedule,
|
||||
enabled = contentFetchConfig.enabled,
|
||||
nextRunAt = calculateNextRunTime(contentFetchConfig.schedule)
|
||||
)
|
||||
val notifySchedule = Schedule(
|
||||
id = "dual_notify_${System.currentTimeMillis()}",
|
||||
kind = "notify",
|
||||
cron = userNotificationConfig.schedule,
|
||||
enabled = userNotificationConfig.enabled,
|
||||
nextRunAt = nextRunTime
|
||||
)
|
||||
|
||||
database.scheduleDao().upsert(fetchSchedule)
|
||||
database.scheduleDao().upsert(notifySchedule)
|
||||
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Log.e("ScheduleHelper", "Failed to schedule dual notification", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule user notification (alarm + database)
|
||||
*
|
||||
* Orchestrates scheduling a user notification via NotifyReceiver and storing in database.
|
||||
*
|
||||
* @param context Application context
|
||||
* @param database Database instance
|
||||
* @param config User notification configuration
|
||||
* @param calculateNextRunTime Function to calculate next run time from cron expression
|
||||
* @return Schedule ID if successful, null otherwise
|
||||
*/
|
||||
suspend fun scheduleUserNotification(
|
||||
context: Context,
|
||||
database: DailyNotificationDatabase,
|
||||
config: UserNotificationConfig,
|
||||
calculateNextRunTime: (String) -> Long
|
||||
): String? {
|
||||
return try {
|
||||
val nextRunTime = calculateNextRunTime(config.schedule)
|
||||
|
||||
// Generate scheduleId before scheduling (needed for stable requestCode)
|
||||
val scheduleId = "notify_${System.currentTimeMillis()}"
|
||||
|
||||
// Schedule AlarmManager notification
|
||||
NotifyReceiver.scheduleExactNotification(
|
||||
context,
|
||||
nextRunTime,
|
||||
config,
|
||||
scheduleId = scheduleId,
|
||||
source = ScheduleSource.INITIAL_SETUP
|
||||
)
|
||||
|
||||
// Store schedule in database
|
||||
val schedule = Schedule(
|
||||
id = scheduleId,
|
||||
kind = "notify",
|
||||
cron = config.schedule,
|
||||
enabled = config.enabled,
|
||||
nextRunAt = nextRunTime
|
||||
)
|
||||
createSchedule(database, schedule)
|
||||
|
||||
scheduleId
|
||||
} catch (e: Exception) {
|
||||
Log.e("ScheduleHelper", "Failed to schedule user notification", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all WorkManager jobs by tags
|
||||
*
|
||||
|
||||
@@ -206,6 +206,74 @@ public final class TimeSafariIntegrationManager {
|
||||
return activeDid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure TimeSafari integration settings
|
||||
*
|
||||
* @param config Configuration options (may include apiServerUrl, did, etc.)
|
||||
*/
|
||||
public void configure(@NonNull org.json.JSONObject config) {
|
||||
try {
|
||||
logger.d("TS: configure() called");
|
||||
|
||||
// Extract and set API server URL if provided
|
||||
if (config.has("apiServerUrl")) {
|
||||
String url = config.optString("apiServerUrl", null);
|
||||
setApiServerUrl(url);
|
||||
}
|
||||
|
||||
// Extract and set active DID if provided
|
||||
if (config.has("did")) {
|
||||
String did = config.optString("did", null);
|
||||
setActiveDid(did);
|
||||
}
|
||||
|
||||
logger.i("TS: Configuration applied");
|
||||
} catch (Exception e) {
|
||||
logger.e("TS: Configuration failed", e);
|
||||
throw new RuntimeException("Configuration failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update starred plan IDs
|
||||
*
|
||||
* Stores the provided plan IDs in SharedPreferences for use by the fetcher.
|
||||
*
|
||||
* @param planIds List of plan IDs to star
|
||||
*/
|
||||
public void updateStarredPlans(@NonNull List<String> planIds) {
|
||||
try {
|
||||
logger.d("TS: updateStarredPlans() called with count=" + planIds.size());
|
||||
|
||||
// Validate all plan IDs are non-empty strings
|
||||
for (int i = 0; i < planIds.size(); i++) {
|
||||
String planId = planIds.get(i);
|
||||
if (planId == null || planId.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("planIds[" + i + "] must be a non-empty string");
|
||||
}
|
||||
}
|
||||
|
||||
// Store in SharedPreferences (matching TestNativeFetcher expectations)
|
||||
SharedPreferences preferences = appContext
|
||||
.getSharedPreferences("daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||
|
||||
// Convert planIds list to JSON array string
|
||||
org.json.JSONArray jsonArray = new org.json.JSONArray();
|
||||
for (String planId : planIds) {
|
||||
jsonArray.put(planId);
|
||||
}
|
||||
|
||||
preferences.edit()
|
||||
.putString("starredPlanIds", jsonArray.toString())
|
||||
.apply();
|
||||
|
||||
logger.i("TS: Starred plans updated: count=" + planIds.size());
|
||||
} catch (Exception e) {
|
||||
logger.e("TS: Failed to update starred plans", e);
|
||||
throw new RuntimeException("Failed to update starred plans", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle DID change - clear caches and reschedule
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user