Files
daily-notification-plugin/examples/native-fetcher-android.kt
Jose Olarte III d8a0eaf413 refactor(android,ios): rename package com.timesafari to org.timesafari.dailynotification
- Android: move plugin source to org/timesafari/dailynotification, update
  namespace, manifest package, and all package/imports; change intent actions
  to org.timesafari.daily.NOTIFICATION and DISMISS
- iOS: update bundle IDs, BGTask identifiers, subsystem labels, and queue
  names in Plugin and Xcode projects
- Capacitor: update plugin class registration and appIds in configs
- Test apps (android-test-app, daily-notification-test, ios-test-app):
  applicationId/bundleId, manifests, ProGuard, scripts, and docs
- Docs: bulk update references; add CONSUMING_APP_MIGRATION_COM_TO_ORG.md
  for consuming app migration

BREAKING CHANGE: Consuming apps must update plugin class to
org.timesafari.dailynotification.DailyNotificationPlugin, manifest
receivers/actions, and iOS BGTask identifiers per migration doc.
2026-03-12 14:26:07 +08:00

154 lines
6.5 KiB
Kotlin

/**
* TimeSafari Native Fetcher Implementation (Android/Kotlin)
*
* Example implementation of NativeNotificationContentFetcher for Android.
* This runs in background workers and does NOT require JavaScript bridge.
*
* @author Matthew Raymer
* @version 1.0.0
*/
package org.timesafari.notification
import org.timesafari.dailynotification.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
/**
* TimeSafari native content fetcher
*
* Implements the NativeNotificationContentFetcher SPI to provide
* TimeSafari-specific notification content fetching for background workers.
*/
class TimeSafariNativeFetcher(
private val api: TimeSafariApi,
private val tokenProvider: TokenProvider,
private val starredPlanIds: List<String>
) : NativeNotificationContentFetcher {
override suspend fun fetchContent(context: FetchContext): List<NotificationContent> {
return withContext(Dispatchers.IO) {
try {
// 1. Get fresh authentication token
val jwt = tokenProvider.freshToken()
// 2. Fetch TimeSafari data in parallel
val offersToPerson = async { api.fetchOffersToPerson(jwt) }
val offersToPlans = async { api.fetchOffersToPlans(jwt) }
val projectUpdates = async {
if (starredPlanIds.isNotEmpty()) {
api.fetchProjectsLastUpdated(starredPlanIds, jwt)
} else {
null
}
}
// Wait for all requests
val offersPersonResult = offersToPerson.await()
val offersPlansResult = offersToPlans.await()
val updatesResult = projectUpdates.await()
// 3. Convert to NotificationContent list
buildList {
// Add offers to person
offersPersonResult?.data?.forEach { offer ->
add(
NotificationContent(
id = "offer_person_${offer.id}",
title = "New Offer: ${offer.title}",
body = offer.description ?: "",
scheduledTime = context.scheduledTime
?: (System.currentTimeMillis() + 3_600_000),
fetchTime = context.fetchTime,
mediaUrl = offer.imageUrl,
ttlSeconds = 86_400, // 24 hours
dedupeKey = "offer_person_${offer.id}_${offer.updatedAt}",
priority = "default",
metadata = mapOf(
"offerId" to offer.id,
"issuerDid" to offer.issuerDid,
"source" to "offers_to_person"
)
)
)
}
// Add offers to plans
offersPlansResult?.data?.forEach { offer ->
add(
NotificationContent(
id = "offer_plan_${offer.id}",
title = "Offer for Your Project: ${offer.projectName}",
body = offer.description ?: "",
scheduledTime = context.scheduledTime
?: (System.currentTimeMillis() + 3_600_000),
fetchTime = context.fetchTime,
mediaUrl = offer.imageUrl,
ttlSeconds = 86_400,
dedupeKey = "offer_plan_${offer.id}_${offer.updatedAt}",
priority = "default",
metadata = mapOf(
"offerId" to offer.id,
"planHandleId" to offer.planHandleId,
"source" to "offers_to_plans"
)
)
)
}
// Add project updates
updatesResult?.data?.forEach { update ->
add(
NotificationContent(
id = "project_${update.planSummary.jwtId}",
title = "${update.planSummary.name} Updated",
body = "New updates for ${update.planSummary.name}",
scheduledTime = context.scheduledTime
?: (System.currentTimeMillis() + 3_600_000),
fetchTime = context.fetchTime,
mediaUrl = update.planSummary.image,
ttlSeconds = 86_400,
dedupeKey = "project_${update.planSummary.handleId}_${update.planSummary.jwtId}",
priority = "default",
metadata = mapOf(
"planHandleId" to update.planSummary.handleId,
"jwtId" to update.planSummary.jwtId,
"source" to "project_updates"
)
)
)
}
}
} catch (e: Exception) {
// Log error and return empty list
// Plugin will handle retry based on SchedulingPolicy
android.util.Log.e("TimeSafariFetcher", "Fetch failed", e)
emptyList()
}
}
}
}
/**
* Registration in Application.onCreate()
*/
class TimeSafariApplication : Application() {
override fun onCreate() {
super.onCreate()
// Register native fetcher
val api = TimeSafariApiClient(context = this)
val tokenProvider = JWTokenProvider(activeDid = getActiveDid())
val starredPlanIds = getStarredPlanIds()
val fetcher = TimeSafariNativeFetcher(
api = api,
tokenProvider = tokenProvider,
starredPlanIds = starredPlanIds
)
DailyNotification.setNativeFetcher(fetcher)
}
}