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>
163 lines
5.7 KiB
Kotlin
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
|
|
)
|