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.
		
		
		
		
		
			
		
			
				
					
					
						
							153 lines
						
					
					
						
							6.5 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							153 lines
						
					
					
						
							6.5 KiB
						
					
					
				
								/**
							 | 
						|
								 * 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 com.timesafari.notification
							 | 
						|
								
							 | 
						|
								import com.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)
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								
							 |