feat(android): add runtime starred plans management API

- Add updateStarredPlans() method to update plan IDs from TimeSafari app
  - Stores plan IDs in SharedPreferences for persistence
  - Integrated with TimeSafariIntegrationManager for prefetch operations
  - Includes comprehensive logging for debugging

- Add getStarredPlans() method to retrieve current stored plan IDs
  - Allows TimeSafari app to verify synchronization
  - Returns count and last update timestamp

- Update TimeSafariIntegrationManager to load starred plan IDs
  - Reads from SharedPreferences when building TimeSafariUserConfig
  - Used automatically by EnhancedDailyNotificationFetcher for API calls
  - Enables dynamic updates without requiring app restart

- Add TypeScript definitions for new methods
  - Includes JSDoc documentation for integration guidance
  - Matches Android implementation return types

- Create integration example for TimeSafari app
  - Shows how to sync plan IDs from account settings
  - Demonstrates star/unstar action handling
  - Includes verification and error handling patterns

This allows the TimeSafari app to dynamically update starred project
IDs when users star or unstar projects, without requiring plugin
configuration changes or app restarts. The stored IDs are automatically
used by the prefetch system to query for project updates.
This commit is contained in:
Matthew Raymer
2025-10-29 11:52:15 +00:00
parent 63a2428cd9
commit fd4ddcbd60
4 changed files with 473 additions and 0 deletions

View File

@@ -1786,6 +1786,126 @@ public class DailyNotificationPlugin extends Plugin {
}
}
/**
* Update starred plan IDs from host application
*
* This allows the TimeSafari app to dynamically update the list of starred
* project IDs when users star or unstar projects. The IDs are stored persistently
* and used for prefetch operations that query for starred project updates.
*
* @param call Contains:
* - planIds: string[] - Array of starred plan handle IDs
*/
@PluginMethod
public void updateStarredPlans(PluginCall call) {
try {
JSObject data = call.getData();
if (data == null) {
call.reject("No data provided");
return;
}
Object planIdsObj = data.get("planIds");
if (planIdsObj == null) {
call.reject("planIds is required");
return;
}
// Convert to List<String>
List<String> planIds;
if (planIdsObj instanceof List) {
@SuppressWarnings("unchecked")
List<Object> objList = (List<Object>) planIdsObj;
planIds = new java.util.ArrayList<>();
for (Object obj : objList) {
if (obj != null) {
planIds.add(obj.toString());
}
}
} else {
call.reject("planIds must be an array");
return;
}
Log.i(TAG, "DN|UPDATE_STARRED_PLANS count=" + planIds.size());
// Store in SharedPreferences for persistence
SharedPreferences preferences = getContext()
.getSharedPreferences("daily_notification_timesafari", Context.MODE_PRIVATE);
// Store as JSON string for easy retrieval
org.json.JSONArray jsonArray = new org.json.JSONArray();
for (String planId : planIds) {
jsonArray.put(planId);
}
preferences.edit()
.putString("starredPlanIds", jsonArray.toString())
.putLong("starredPlansUpdatedAt", System.currentTimeMillis())
.apply();
Log.d(TAG, "DN|STARRED_PLANS_STORED count=" + planIds.size() +
" stored_at=" + System.currentTimeMillis());
// Update TimeSafariIntegrationManager if it needs the IDs immediately
if (timeSafariIntegration != null) {
// The TimeSafariIntegrationManager will read from SharedPreferences
// when it needs the starred plan IDs, so no direct update needed
Log.d(TAG, "DN|STARRED_PLANS_UPDATED TimeSafariIntegrationManager will use stored IDs");
}
JSObject result = new JSObject();
result.put("success", true);
result.put("planIdsCount", planIds.size());
result.put("updatedAt", System.currentTimeMillis());
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "DN|UPDATE_STARRED_PLANS_ERR Error updating starred plans", e);
call.reject("Error updating starred plans: " + e.getMessage());
}
}
/**
* Get current starred plan IDs
*
* Returns the currently stored starred plan IDs from SharedPreferences.
* This is useful for the host app to verify what IDs are stored.
*/
@PluginMethod
public void getStarredPlans(PluginCall call) {
try {
SharedPreferences preferences = getContext()
.getSharedPreferences("daily_notification_timesafari", Context.MODE_PRIVATE);
String starredPlansJson = preferences.getString("starredPlanIds", "[]");
long updatedAt = preferences.getLong("starredPlansUpdatedAt", 0);
org.json.JSONArray jsonArray = new org.json.JSONArray(starredPlansJson);
List<String> planIds = new java.util.ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
planIds.add(jsonArray.getString(i));
}
JSObject result = new JSObject();
org.json.JSONArray planIdsArray = new org.json.JSONArray();
for (String planId : planIds) {
planIdsArray.put(planId);
}
result.put("planIds", planIdsArray);
result.put("count", planIds.size());
result.put("updatedAt", updatedAt);
Log.d(TAG, "DN|GET_STARRED_PLANS count=" + planIds.size());
call.resolve(result);
} catch (Exception e) {
Log.e(TAG, "DN|GET_STARRED_PLANS_ERR Error getting starred plans", e);
call.reject("Error getting starred plans: " + e.getMessage());
}
}
/**
* Test JWT generation for debugging
*/

View File

@@ -27,18 +27,23 @@
package com.timesafari.dailynotification;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.json.JSONArray;
import org.json.JSONException;
/**
* TimeSafari Integration Manager
*
@@ -272,6 +277,11 @@ public final class TimeSafariIntegrationManager {
userConfig.fetchOffersToProjects = true;
userConfig.fetchProjectUpdates = true;
// Load starred plan IDs from SharedPreferences
userConfig.starredPlanIds = loadStarredPlanIdsFromSharedPreferences();
logger.d("TS: Loaded starredPlanIds count=" +
(userConfig.starredPlanIds != null ? userConfig.starredPlanIds.size() : 0));
// 3) Execute fetch (async, but we wait in executor)
CompletableFuture<EnhancedDailyNotificationFetcher.TimeSafariNotificationBundle> future =
fetcher.fetchAllTimeSafariData(userConfig);
@@ -584,5 +594,47 @@ public final class TimeSafariIntegrationManager {
// If you replace the Executor with something closeable, do it here
// For now, single-threaded executor will be GC'd when manager is GC'd
}
/* ============================================================
* Helper Methods
* ============================================================ */
/**
* Load starred plan IDs from SharedPreferences
*
* Reads the persisted starred plan IDs that were stored via
* DailyNotificationPlugin.updateStarredPlans()
*
* @return List of starred plan IDs, or empty list if none stored
*/
@NonNull
private List<String> loadStarredPlanIdsFromSharedPreferences() {
try {
SharedPreferences preferences = appContext
.getSharedPreferences("daily_notification_timesafari", Context.MODE_PRIVATE);
String starredPlansJson = preferences.getString("starredPlanIds", "[]");
if (starredPlansJson == null || starredPlansJson.isEmpty()) {
return new ArrayList<>();
}
JSONArray jsonArray = new JSONArray(starredPlansJson);
List<String> planIds = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
planIds.add(jsonArray.getString(i));
}
return planIds;
} catch (JSONException e) {
logger.e("TS: Error parsing starredPlanIds from SharedPreferences", e);
return new ArrayList<>();
} catch (Exception e) {
logger.e("TS: Unexpected error loading starredPlanIds", e);
return new ArrayList<>();
}
}
}