feat(android): improve New Activity notification copy in TimeSafariNativeFetcher

Aggregate API rows into one notification with Starred Project Update(s) titles,
plan names in typographic quotes, and "+ N more have been updated." for multiples.
Stop emitting the empty-data "No Project Updates" fallback. Sync internal docs.
This commit is contained in:
Jose Olarte III
2026-03-31 19:50:14 +08:00
parent 230dc52974
commit 8ba84888ee
3 changed files with 62 additions and 38 deletions

View File

@@ -325,44 +325,68 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
}
}
/**
* Display title for a plansLastUpdatedBetween row; prefers {@code plan.name}, else "Unnamed Project".
*/
private String extractProjectDisplayTitle(JsonObject item) {
if (item.has("plan")) {
JsonObject plan = item.getAsJsonObject("plan");
if (plan.has("name") && !plan.get("name").isJsonNull()) {
String name = plan.get("name").getAsString();
if (name != null && !name.trim().isEmpty()) {
return name.trim();
}
}
}
return "Unnamed Project";
}
@Nullable
private String extractJwtIdFromItem(JsonObject item) {
if (item.has("plan")) {
JsonObject plan = item.getAsJsonObject("plan");
if (plan.has("jwtId") && !plan.get("jwtId").isJsonNull()) {
return plan.get("jwtId").getAsString();
}
}
if (item.has("jwtId") && !item.get("jwtId").isJsonNull()) {
return item.get("jwtId").getAsString();
}
return null;
}
private List<NotificationContent> parseApiResponse(String responseBody, FetchContext context) {
List<NotificationContent> contents = new ArrayList<>();
try {
JsonObject root = JsonParser.parseString(responseBody).getAsJsonObject();
JsonArray dataArray = root.has("data") ? root.getAsJsonArray("data") : null;
if (dataArray != null) {
for (int i = 0; i < dataArray.size(); i++) {
JsonObject item = dataArray.get(i).getAsJsonObject();
NotificationContent content = new NotificationContent();
String planId = null;
String jwtId = null;
if (item.has("plan")) {
JsonObject plan = item.getAsJsonObject("plan");
if (plan.has("handleId")) planId = plan.get("handleId").getAsString();
if (plan.has("jwtId")) jwtId = plan.get("jwtId").getAsString();
}
if (planId == null && item.has("planId")) planId = item.get("planId").getAsString();
if (jwtId == null && item.has("jwtId")) jwtId = item.get("jwtId").getAsString();
if (dataArray == null || dataArray.size() == 0) {
return contents;
}
content.setId("endorser_" + (jwtId != null ? jwtId : (System.currentTimeMillis() + "_" + i)));
content.setTitle(planId != null ? "Update: " + planId.substring(Math.max(0, planId.length() - 8)) : "Project Update");
content.setBody(planId != null ? "Plan " + planId.substring(Math.max(0, planId.length() - 12)) + " has been updated." : "A project you follow has been updated.");
content.setScheduledTime(context.scheduledTime != null ? context.scheduledTime : (System.currentTimeMillis() + 3600000));
content.setPriority("default");
content.setSound(true);
contents.add(content);
}
}
if (contents.isEmpty()) {
NotificationContent defaultContent = new NotificationContent();
defaultContent.setId("endorser_no_updates_" + System.currentTimeMillis());
defaultContent.setTitle("No Project Updates");
defaultContent.setBody("No updates in your starred projects.");
defaultContent.setScheduledTime(context.scheduledTime != null ? context.scheduledTime : (System.currentTimeMillis() + 3600000));
defaultContent.setPriority("default");
defaultContent.setSound(true);
contents.add(defaultContent);
JsonObject firstItem = dataArray.get(0).getAsJsonObject();
String firstTitle = extractProjectDisplayTitle(firstItem);
String jwtId = extractJwtIdFromItem(firstItem);
NotificationContent content = new NotificationContent();
content.setId("endorser_" + (jwtId != null ? jwtId : ("batch_" + System.currentTimeMillis())));
int n = dataArray.size();
String quotedFirst = "\u201C" + firstTitle + "\u201D";
if (n == 1) {
content.setTitle("Starred Project Update");
content.setBody(quotedFirst + " has been updated.");
} else {
content.setTitle("Starred Project Updates");
int more = n - 1;
content.setBody(quotedFirst + " + " + more + " more have been updated.");
}
content.setScheduledTime(
context.scheduledTime != null
? context.scheduledTime
: (System.currentTimeMillis() + 3600000));
content.setPriority("default");
content.setSound(true);
contents.add(content);
} catch (Exception e) {
Log.e(TAG, "Error parsing API response", e);
}