Files
honeyDueKMP/composeApp/src/commonMain/kotlin/com/example/mycrib/models/CustomTask.kt
Trey t 7b0a0e5d85 Implement Android subscription system with freemium limitations
Major subscription system implementation for Android:

BillingManager (Android):
- Full Google Play Billing Library integration
- Product loading, purchase flow, and acknowledgment
- Backend verification via APILayer.verifyAndroidPurchase()
- Purchase restoration for returning users
- Error handling and connection state management

SubscriptionHelper (Shared):
- New limit checking methods: isResidencesBlocked(), isTasksBlocked(),
  isContractorsBlocked(), isDocumentsBlocked()
- Add permission checks: canAddProperty(), canAddTask(),
  canAddContractor(), canAddDocument()
- Enforces freemium rules based on backend limitationsEnabled flag

Screen Updates:
- ContractorsScreen: Show upgrade prompt when contractors limit=0
- DocumentsScreen: Show upgrade prompt when documents limit=0
- ResidencesScreen: Show upgrade prompt when properties limit reached
- ResidenceDetailScreen: Show upgrade prompt when tasks limit reached

UpgradeFeatureScreen:
- Enhanced with feature benefits comparison
- Dynamic content from backend upgrade triggers
- Platform-specific purchase buttons

Additional changes:
- DataCache: Added O(1) lookup maps for ID resolution
- New minimal models (TaskMinimal, ContractorMinimal, ResidenceMinimal)
- TaskApi: Added archive/unarchive endpoints
- Added Google Billing Library dependency
- iOS SubscriptionCache and UpgradePromptView updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 11:23:53 -06:00

163 lines
5.7 KiB
Kotlin

package com.example.mycrib.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class CustomTask (
val id: Int,
val residence: Int,
@SerialName("created_by") val createdBy: Int,
@SerialName("created_by_username") val createdByUsername: String,
val title: String,
val description: String? = null,
val category: TaskCategory?,
val frequency: TaskFrequency,
val priority: TaskPriority,
val status: TaskStatus? = null,
@SerialName("due_date") val dueDate: String?,
@SerialName("next_scheduled_date") val nextScheduledDate: String? = null,
@SerialName("estimated_cost") val estimatedCost: Double? = null,
@SerialName("actual_cost") val actualCost: Double? = null,
@SerialName("completion_count") val completionCount: Int? = null,
val notes: String? = null,
val archived: Boolean = false,
@SerialName("created_at") val createdAt: String,
@SerialName("updated_at") val updatedAt: String,
@SerialName("show_completed_button") val showCompletedButton: Boolean = false,
@SerialName("days_until_due") val daysUntilDue: Int? = null,
@SerialName("is_overdue") val isOverdue: Boolean? = null,
@SerialName("last_completion") val lastCompletion: LastCompletion? = null
)
@Serializable
data class LastCompletion(
@SerialName("completion_date") val completionDate: String,
@SerialName("completed_by") val completedBy: String?,
@SerialName("actual_cost") val actualCost: Double?,
val rating: Int?
)
@Serializable
data class TaskCreateRequest(
val residence: Int,
val title: String,
val description: String? = null,
val category: Int,
val frequency: Int,
@SerialName("interval_days") val intervalDays: Int? = null,
val priority: Int,
val status: Int? = null,
@SerialName("due_date") val dueDate: String,
@SerialName("estimated_cost") val estimatedCost: Double? = null,
val archived: Boolean = false
)
@Serializable
data class TaskDetail(
val id: Int,
val residence: Int,
@SerialName("residence_name") val residenceName: String? = null,
@SerialName("created_by") val createdBy: Int? = null,
@SerialName("created_by_username") val createdByUsername: String? = null,
val title: String,
val description: String?,
val category: TaskCategory,
val priority: TaskPriority,
val frequency: TaskFrequency,
val status: TaskStatus?,
@SerialName("due_date") val dueDate: String?,
@SerialName("interval_days") val intervalDays: Int? = null,
@SerialName("estimated_cost") val estimatedCost: Double? = null,
val archived: Boolean = false,
@SerialName("created_at") val createdAt: String,
@SerialName("updated_at") val updatedAt: String,
@SerialName("next_scheduled_date") val nextScheduledDate: String? = null,
@SerialName("show_completed_button") val showCompletedButton: Boolean = false,
val completions: List<TaskCompletion>
)
@Serializable
data class TasksByResidenceResponse(
@SerialName("residence_id") val residenceId: String,
@SerialName("days_threshold") val daysThreshold: Int,
val summary: CategorizedTaskSummary,
@SerialName("upcoming_tasks") val upcomingTasks: List<TaskDetail>,
@SerialName("in_progress_tasks") val inProgressTasks: List<TaskDetail>,
@SerialName("done_tasks") val doneTasks: List<TaskDetail>,
@SerialName("archived_tasks") val archivedTasks: List<TaskDetail>
)
@Serializable
data class CategorizedTaskSummary(
val upcoming: Int,
@SerialName("in_progress") val inProgress: Int,
val done: Int,
val archived: Int
)
@Serializable
data class AllTasksResponse(
@SerialName("days_threshold") val daysThreshold: Int,
val summary: CategorizedTaskSummary,
@SerialName("upcoming_tasks") val upcomingTasks: List<TaskDetail>,
@SerialName("in_progress_tasks") val inProgressTasks: List<TaskDetail>,
@SerialName("done_tasks") val doneTasks: List<TaskDetail>,
@SerialName("archived_tasks") val archivedTasks: List<TaskDetail>
)
@Serializable
data class TaskCancelResponse(
val message: String,
val task: TaskDetail
)
/**
* Request model for PATCH updates to a task.
* Used for status changes and archive/unarchive operations.
* All fields are optional - only provided fields will be updated.
*/
@Serializable
data class TaskPatchRequest(
val status: Int? = null, // Status ID to update
val archived: Boolean? = null // Archive/unarchive flag
)
/**
* Minimal task model for list/kanban views.
* Uses IDs instead of nested objects for efficiency.
* Resolve IDs to full objects via DataCache.getTaskCategory(), etc.
*/
@Serializable
data class TaskMinimal(
val id: Int,
val title: String,
val description: String? = null,
@SerialName("due_date") val dueDate: String? = null,
@SerialName("next_scheduled_date") val nextScheduledDate: String? = null,
@SerialName("category_id") val categoryId: Int? = null,
@SerialName("frequency_id") val frequencyId: Int,
@SerialName("priority_id") val priorityId: Int,
@SerialName("status_id") val statusId: Int? = null,
@SerialName("completion_count") val completionCount: Int? = null,
val archived: Boolean = false
)
@Serializable
data class TaskColumn(
val name: String,
@SerialName("display_name") val displayName: String,
@SerialName("button_types") val buttonTypes: List<String>,
val icons: Map<String, String>,
val color: String,
val tasks: List<TaskDetail>, // Keep using TaskDetail for now - will be TaskMinimal after full migration
val count: Int
)
@Serializable
data class TaskColumnsResponse(
val columns: List<TaskColumn>,
@SerialName("days_threshold") val daysThreshold: Int? = null,
@SerialName("residence_id") val residenceId: String? = null
)