Integration: residence invite accept/decline APIs + wire notification actions

Adds acceptResidenceInvite / declineResidenceInvite to ResidenceApi
(POST /api/residences/{id}/invite/{accept|decline}) and exposes them via
APILayer. On accept success, myResidences is force-refreshed so the
newly-joined residence appears without a manual pull.

Wires NotificationActionReceiver's ACCEPT_INVITE / DECLINE_INVITE
handlers to the new APILayer calls, replacing the log-only TODOs left
behind by P4 Stream O. Notifications are now cleared only on API
success so a failed accept stays actionable.

Tests:
 - ResidenceApiInviteTest covers correct HTTP method/path + error surfacing.
 - NotificationActionReceiverTest invite cases updated to assert the new
   APILayer calls (were previously asserting the log-only path).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-18 13:36:59 -05:00
parent 10b57aabaa
commit 485f70dfa1
5 changed files with 227 additions and 28 deletions

View File

@@ -212,39 +212,39 @@ class NotificationActionReceiver : BroadcastReceiver() {
// ---------------- Accept / Decline invite ----------------
private fun handleAcceptInvite(context: Context, residenceId: Long?, notificationId: Int) {
private suspend fun handleAcceptInvite(context: Context, residenceId: Long?, notificationId: Int) {
if (residenceId == null) {
Log.w(TAG, "ACCEPT_INVITE without residence_id — no-op")
return
}
// APILayer.acceptResidenceInvite does not yet exist — see TODO below.
// composeApp/src/commonMain/kotlin/com/tt/honeyDue/network/APILayer.kt
// (add after createTaskCompletion at ~line 790). Backend endpoint
// intended at POST /api/residences/{id}/invite/accept.
Log.w(
TAG,
"TODO: APILayer.acceptResidenceInvite($residenceId) — implement in " +
"composeApp/src/commonMain/kotlin/com/tt/honeyDue/network/APILayer.kt " +
"(follow createTaskCompletion pattern; POST /api/residences/{id}/invite/accept)"
)
// Best-effort UX: cancel the notification so the user isn't stuck
// on a button that does nothing visible. The invite will be picked
// up on next app-open from /api/residences/pending.
cancelNotification(context, notificationId)
when (val result = APILayer.acceptResidenceInvite(residenceId.toInt())) {
is ApiResult.Success -> {
Log.d(TAG, "Residence invite $residenceId accepted")
cancelNotification(context, notificationId)
}
is ApiResult.Error -> {
// Leave the notification so the user can retry from the app.
Log.e(TAG, "Accept invite failed: ${result.message}")
}
else -> Log.w(TAG, "Unexpected ApiResult from acceptResidenceInvite")
}
}
private fun handleDeclineInvite(context: Context, residenceId: Long?, notificationId: Int) {
private suspend fun handleDeclineInvite(context: Context, residenceId: Long?, notificationId: Int) {
if (residenceId == null) {
Log.w(TAG, "DECLINE_INVITE without residence_id — no-op")
return
}
Log.w(
TAG,
"TODO: APILayer.declineResidenceInvite($residenceId) — implement in " +
"composeApp/src/commonMain/kotlin/com/tt/honeyDue/network/APILayer.kt " +
"(follow createTaskCompletion pattern; POST /api/residences/{id}/invite/decline)"
)
cancelNotification(context, notificationId)
when (val result = APILayer.declineResidenceInvite(residenceId.toInt())) {
is ApiResult.Success -> {
Log.d(TAG, "Residence invite $residenceId declined")
cancelNotification(context, notificationId)
}
is ApiResult.Error -> {
Log.e(TAG, "Decline invite failed: ${result.message}")
}
else -> Log.w(TAG, "Unexpected ApiResult from declineResidenceInvite")
}
}
// ---------------- helpers ----------------