Files
honeyDueKMP/composeApp/src/androidMain/kotlin/com/tt/honeyDue/NotificationActionReceiver.kt
T
Trey t b2d03ef8b2
Android UI Tests / ui-tests (pull_request) Has been cancelled
refactor(uploads): drop legacy multipart helpers; route Android UI through presigned flow
The KMP shared layer's task-completion-with-images path now exclusively
uses the presigned-URL flow: each image is compressed, uploaded directly
to B2 via APILayer.uploadImage, and the resulting upload_ids are passed
to /api/task-completions/ as JSON. Bytes never traverse our API server.

Changes:
  - TaskCompletionViewModel.createTaskCompletionWithImages now does the
    presign→POST→collect-ids dance internally. The signature stays the
    same so the three Android UI call sites (TasksScreen, AllTasksScreen,
    ResidenceDetailScreen, CompleteTaskDialog, CompleteTaskScreen) need
    no changes.
  - APILayer.createTaskCompletionWithImages removed (dead).
  - TaskCompletionApi.createCompletionWithImages removed (the multipart
    HTTP helper that posted to the legacy POST /api/task-completions/
    multipart endpoint).
  - TaskCompletionCreateRequest.imageUrls field removed.
  - Three Swift call sites (CompleteTaskView, WidgetActionProcessor,
    PushNotificationManager) updated to drop the imageUrls argument.
  - Two Kotlin call sites (CompleteTaskDialog, CompleteTaskScreen) updated.

Image uploads now match WhatsApp/Slack-class architecture: client-side
compression + direct-to-storage upload + lightweight JSON entity create.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:48:11 -07:00

171 lines
5.9 KiB
Kotlin

package com.tt.honeyDue
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.app.NotificationManagerCompat
import com.tt.honeyDue.data.DataManager
import com.tt.honeyDue.models.TaskCompletionCreateRequest
import com.tt.honeyDue.network.APILayer
import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.storage.TokenStorage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
/**
* BroadcastReceiver for handling notification action button clicks.
* Performs task actions (complete, cancel, etc.) directly from notifications.
*/
class NotificationActionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action ?: return
val taskId = intent.getIntExtra(EXTRA_TASK_ID, -1)
val notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, 0)
Log.d(TAG, "Action received: $action for task $taskId")
if (taskId == -1) {
Log.e(TAG, "No task ID provided")
return
}
// Dismiss the notification
NotificationManagerCompat.from(context).cancel(notificationId)
// Check subscription status
val isPremium = isPremiumUser()
if (!isPremium) {
Log.d(TAG, "Non-premium user, ignoring action")
launchMainActivity(context, null)
return
}
// Handle action
when (action) {
ACTION_VIEW_TASK -> {
launchMainActivity(context, taskId)
}
ACTION_COMPLETE_TASK -> {
performCompleteTask(context, taskId)
}
ACTION_MARK_IN_PROGRESS -> {
performMarkInProgress(context, taskId)
}
ACTION_CANCEL_TASK -> {
performCancelTask(context, taskId)
}
ACTION_UNCANCEL_TASK -> {
performUncancelTask(context, taskId)
}
else -> {
Log.w(TAG, "Unknown action: $action")
}
}
}
private fun isPremiumUser(): Boolean {
val subscription = DataManager.subscription.value
// User is premium if limitations are disabled
return subscription?.limitationsEnabled == false
}
private fun launchMainActivity(context: Context, taskId: Int?) {
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
if (taskId != null) {
putExtra(EXTRA_NAVIGATE_TO_TASK, taskId)
}
}
context.startActivity(intent)
}
private fun performCompleteTask(context: Context, taskId: Int) {
Log.d(TAG, "Completing task $taskId")
CoroutineScope(Dispatchers.IO).launch {
val request = TaskCompletionCreateRequest(
taskId = taskId,
completedAt = null,
notes = null,
actualCost = null,
rating = null,
)
when (val result = APILayer.createTaskCompletion(request)) {
is ApiResult.Success -> {
Log.d(TAG, "Task $taskId completed successfully")
// Launch app to show result
launchMainActivity(context, taskId)
}
is ApiResult.Error -> {
Log.e(TAG, "Failed to complete task: ${result.message}")
}
else -> {}
}
}
}
private fun performMarkInProgress(context: Context, taskId: Int) {
Log.d(TAG, "Marking task $taskId as in progress")
CoroutineScope(Dispatchers.IO).launch {
when (val result = APILayer.markInProgress(taskId)) {
is ApiResult.Success -> {
Log.d(TAG, "Task $taskId marked as in progress")
}
is ApiResult.Error -> {
Log.e(TAG, "Failed to mark task in progress: ${result.message}")
}
else -> {}
}
}
}
private fun performCancelTask(context: Context, taskId: Int) {
Log.d(TAG, "Cancelling task $taskId")
CoroutineScope(Dispatchers.IO).launch {
when (val result = APILayer.cancelTask(taskId)) {
is ApiResult.Success -> {
Log.d(TAG, "Task $taskId cancelled")
}
is ApiResult.Error -> {
Log.e(TAG, "Failed to cancel task: ${result.message}")
}
else -> {}
}
}
}
private fun performUncancelTask(context: Context, taskId: Int) {
Log.d(TAG, "Uncancelling task $taskId")
CoroutineScope(Dispatchers.IO).launch {
when (val result = APILayer.uncancelTask(taskId)) {
is ApiResult.Success -> {
Log.d(TAG, "Task $taskId uncancelled")
}
is ApiResult.Error -> {
Log.e(TAG, "Failed to uncancel task: ${result.message}")
}
else -> {}
}
}
}
companion object {
private const val TAG = "NotificationAction"
// Action constants
const val ACTION_VIEW_TASK = "com.tt.honeyDue.ACTION_VIEW_TASK"
const val ACTION_COMPLETE_TASK = "com.tt.honeyDue.ACTION_COMPLETE_TASK"
const val ACTION_MARK_IN_PROGRESS = "com.tt.honeyDue.ACTION_MARK_IN_PROGRESS"
const val ACTION_CANCEL_TASK = "com.tt.honeyDue.ACTION_CANCEL_TASK"
const val ACTION_UNCANCEL_TASK = "com.tt.honeyDue.ACTION_UNCANCEL_TASK"
// Extra constants
const val EXTRA_TASK_ID = "task_id"
const val EXTRA_NOTIFICATION_ID = "notification_id"
const val EXTRA_NAVIGATE_TO_TASK = "navigate_to_task"
}
}