You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
294 lines
12 KiB
294 lines
12 KiB
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
|
|
}
|
|
}
|
|
|