package com.timesafari.dailynotification import android.content.Context 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 kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.json.JSONObject /** * Main Android implementation of Daily Notification Plugin * Bridges Capacitor calls to native Android functionality * * @author Matthew Raymer * @version 1.1.0 */ @CapacitorPlugin(name = "DailyNotification") class DailyNotificationPlugin : Plugin() { companion object { private const val TAG = "DNP-PLUGIN" } private lateinit var db: DailyNotificationDatabase override fun load() { super.load() db = DailyNotificationDatabase.getDatabase(context) Log.i(TAG, "Daily Notification Plugin loaded") } @PluginMethod fun configure(call: PluginCall) { try { val options = call.getObject("options") Log.i(TAG, "Configure called with options: $options") // Store configuration in database CoroutineScope(Dispatchers.IO).launch { try { // Implementation would store config in database call.resolve() } catch (e: Exception) { Log.e(TAG, "Failed to configure", e) call.reject("Configuration failed: ${e.message}") } } } catch (e: Exception) { Log.e(TAG, "Configure error", e) call.reject("Configuration error: ${e.message}") } } @PluginMethod fun scheduleContentFetch(call: PluginCall) { try { val configJson = call.getObject("config") val config = parseContentFetchConfig(configJson) Log.i(TAG, "Scheduling content fetch") CoroutineScope(Dispatchers.IO).launch { try { // Schedule WorkManager fetch FetchWorker.scheduleFetch(context, config) // Store schedule in database val schedule = Schedule( id = "fetch_${System.currentTimeMillis()}", kind = "fetch", cron = config.schedule, enabled = config.enabled, nextRunAt = calculateNextRunTime(config.schedule) ) db.scheduleDao().upsert(schedule) call.resolve() } catch (e: Exception) { Log.e(TAG, "Failed to schedule content fetch", e) call.reject("Content fetch scheduling failed: ${e.message}") } } } catch (e: Exception) { Log.e(TAG, "Schedule content fetch error", e) call.reject("Content fetch error: ${e.message}") } } @PluginMethod fun scheduleUserNotification(call: PluginCall) { try { val configJson = call.getObject("config") val config = parseUserNotificationConfig(configJson) Log.i(TAG, "Scheduling user notification") CoroutineScope(Dispatchers.IO).launch { try { val nextRunTime = calculateNextRunTime(config.schedule) // Schedule AlarmManager notification NotifyReceiver.scheduleExactNotification(context, nextRunTime, config) // Store schedule in database val schedule = Schedule( id = "notify_${System.currentTimeMillis()}", kind = "notify", cron = config.schedule, enabled = config.enabled, nextRunAt = nextRunTime ) db.scheduleDao().upsert(schedule) call.resolve() } catch (e: Exception) { Log.e(TAG, "Failed to schedule user notification", e) call.reject("User notification scheduling failed: ${e.message}") } } } catch (e: Exception) { Log.e(TAG, "Schedule user notification error", e) call.reject("User notification error: ${e.message}") } } @PluginMethod fun scheduleDualNotification(call: PluginCall) { try { val configJson = call.getObject("config") val contentFetchConfig = parseContentFetchConfig(configJson.getObject("contentFetch")) val userNotificationConfig = parseUserNotificationConfig(configJson.getObject("userNotification")) Log.i(TAG, "Scheduling dual notification") CoroutineScope(Dispatchers.IO).launch { try { // Schedule both fetch and notification FetchWorker.scheduleFetch(context, contentFetchConfig) val nextRunTime = calculateNextRunTime(userNotificationConfig.schedule) NotifyReceiver.scheduleExactNotification(context, nextRunTime, userNotificationConfig) // 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 ) db.scheduleDao().upsert(fetchSchedule) db.scheduleDao().upsert(notifySchedule) call.resolve() } catch (e: Exception) { Log.e(TAG, "Failed to schedule dual notification", e) call.reject("Dual notification scheduling failed: ${e.message}") } } } catch (e: Exception) { Log.e(TAG, "Schedule dual notification error", e) call.reject("Dual notification error: ${e.message}") } } @PluginMethod fun getDualScheduleStatus(call: PluginCall) { CoroutineScope(Dispatchers.IO).launch { try { val enabledSchedules = db.scheduleDao().getEnabled() val latestCache = db.contentCacheDao().getLatest() val recentHistory = db.historyDao().getSince(System.currentTimeMillis() - (24 * 60 * 60 * 1000L)) val status = JSObject().apply { put("nextRuns", enabledSchedules.map { it.nextRunAt }) put("lastOutcomes", recentHistory.map { it.outcome }) put("cacheAgeMs", latestCache?.let { System.currentTimeMillis() - it.fetchedAt }) put("staleArmed", latestCache?.let { System.currentTimeMillis() > (it.fetchedAt + it.ttlSeconds * 1000L) } ?: true) put("queueDepth", recentHistory.size) } call.resolve(status) } catch (e: Exception) { Log.e(TAG, "Failed to get dual schedule status", e) call.reject("Status retrieval failed: ${e.message}") } } } @PluginMethod fun registerCallback(call: PluginCall) { try { val name = call.getString("name") val callback = call.getObject("callback") Log.i(TAG, "Registering callback: $name") CoroutineScope(Dispatchers.IO).launch { try { val callbackRecord = Callback( id = name, kind = callback.getString("kind", "local"), target = callback.getString("target", ""), headersJson = callback.getString("headers"), enabled = true, createdAt = System.currentTimeMillis() ) db.callbackDao().upsert(callbackRecord) call.resolve() } catch (e: Exception) { Log.e(TAG, "Failed to register callback", e) call.reject("Callback registration failed: ${e.message}") } } } catch (e: Exception) { Log.e(TAG, "Register callback error", e) call.reject("Callback registration error: ${e.message}") } } @PluginMethod fun getContentCache(call: PluginCall) { CoroutineScope(Dispatchers.IO).launch { try { val latestCache = db.contentCacheDao().getLatest() val result = JSObject() if (latestCache != null) { result.put("id", latestCache.id) result.put("fetchedAt", latestCache.fetchedAt) result.put("ttlSeconds", latestCache.ttlSeconds) result.put("payload", String(latestCache.payload)) result.put("meta", latestCache.meta) } call.resolve(result) } catch (e: Exception) { Log.e(TAG, "Failed to get content cache", e) call.reject("Content cache retrieval failed: ${e.message}") } } } // Helper methods private fun parseContentFetchConfig(configJson: JSObject): ContentFetchConfig { return ContentFetchConfig( enabled = configJson.getBoolean("enabled", true), schedule = configJson.getString("schedule", "0 9 * * *"), url = configJson.getString("url"), timeout = configJson.getInt("timeout"), retryAttempts = configJson.getInt("retryAttempts"), retryDelay = configJson.getInt("retryDelay"), callbacks = CallbackConfig( apiService = configJson.getObject("callbacks")?.getString("apiService"), database = configJson.getObject("callbacks")?.getString("database"), reporting = configJson.getObject("callbacks")?.getString("reporting") ) ) } private fun parseUserNotificationConfig(configJson: JSObject): UserNotificationConfig { return UserNotificationConfig( enabled = configJson.getBoolean("enabled", true), schedule = configJson.getString("schedule", "0 9 * * *"), title = configJson.getString("title"), body = configJson.getString("body"), sound = configJson.getBoolean("sound"), vibration = configJson.getBoolean("vibration"), priority = configJson.getString("priority") ) } private fun calculateNextRunTime(schedule: String): Long { // Simple implementation - for production, use proper cron parsing val now = System.currentTimeMillis() return now + (24 * 60 * 60 * 1000L) // Next day } }