Add custom interval days support for task frequency
- Add customIntervalDays field to Kotlin models (TaskResponse, TaskCreateRequest, TaskUpdateRequest) - Update Android AddTaskDialog to show interval field only for "Custom" frequency - Update Android EditTaskScreen for custom frequency support - Update iOS TaskFormView for custom frequency support - Fix preview data in TaskCard and TasksSection to include new field - Add customIntervalDays to OnboardingFirstTaskView 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16977,6 +16977,9 @@
|
||||
"Enter the 6-digit code from your email" : {
|
||||
"comment" : "A footer label explaining that users should enter the 6-digit code they received in their email.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Enter the number of days between each occurrence" : {
|
||||
|
||||
},
|
||||
"Enter your email address and we'll send you a verification code" : {
|
||||
"comment" : "A description below the email input field, instructing the user to enter their email address to receive a password reset code.",
|
||||
@@ -17747,6 +17750,72 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_benefit_actionable_notifications" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Actionable Notifications"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_benefit_contractor_sharing" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Contractor Sharing"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_benefit_document_vault" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Document & Warranty Storage"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_benefit_residence_sharing" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Residence Sharing"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_benefit_unlimited_properties" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Unlimited Properties"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_benefit_widgets" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Home Screen Widgets"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_contact" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -17834,6 +17903,136 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_daily_digest" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Tägliche Zusammenfassung"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Daily Summary"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Resumen diario"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Résumé quotidien"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Riepilogo giornaliero"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "デイリーサマリー"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "일일 요약"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dagelijkse samenvatting"
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Resumo diário"
|
||||
}
|
||||
},
|
||||
"zh" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "每日摘要"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_daily_digest_description" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Tägliche Übersicht über fällige und überfällige Aufgaben"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Daily overview of tasks due and overdue"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Resumen diario de tareas pendientes y vencidas"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Aperçu quotidien des tâches à faire et en retard"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Panoramica giornaliera delle attività in scadenza e scadute"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "期限が近いタスクと期限切れのタスクの毎日の概要"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "마감 예정 및 지연된 작업의 일일 개요"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dagelijks overzicht van taken die binnenkort verlopen en achterstallig zijn"
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Resumo diário de tarefas a vencer e atrasadas"
|
||||
}
|
||||
},
|
||||
"zh" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "即将到期和逾期任务的每日概览"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_edit_profile" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -19557,83 +19756,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_unlock_premium_features" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Unlock Premium Features"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_benefit_unlimited_properties" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Unlimited Properties"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_benefit_document_vault" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Document & Warranty Storage"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_benefit_residence_sharing" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Residence Sharing"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_benefit_contractor_sharing" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Contractor Sharing"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_benefit_actionable_notifications" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Actionable Notifications"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_benefit_widgets" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Home Screen Widgets"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_support" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -20360,6 +20482,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_unlock_premium_features" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Unlock Premium Features"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_upgrade_to_pro" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -20620,136 +20753,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_daily_digest" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Tägliche Zusammenfassung"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Daily Summary"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Resumen diario"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Résumé quotidien"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Riepilogo giornaliero"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "デイリーサマリー"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "일일 요약"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dagelijkse samenvatting"
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Resumo diário"
|
||||
}
|
||||
},
|
||||
"zh" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "每日摘要"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile_daily_digest_description" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Tägliche Übersicht über fällige und überfällige Aufgaben"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Daily overview of tasks due and overdue"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Resumen diario de tareas pendientes y vencidas"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Aperçu quotidien des tâches à faire et en retard"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Panoramica giornaliera delle attività in scadenza e scadute"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "期限が近いタスクと期限切れのタスクの毎日の概要"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "마감 예정 및 지연된 작업의 일일 개요"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dagelijks overzicht van taken die binnenkort verlopen en achterstallig zijn"
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Resumo diário de tarefas a vencer e atrasadas"
|
||||
}
|
||||
},
|
||||
"zh" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "即将到期和逾期任务的每日概览"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"properties_add_button" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
|
||||
@@ -363,6 +363,7 @@ struct OnboardingFirstTaskContent: View {
|
||||
priorityId: nil,
|
||||
inProgress: false,
|
||||
frequencyId: frequencyId.map { KotlinInt(int: $0) },
|
||||
customIntervalDays: nil,
|
||||
assignedToId: nil,
|
||||
dueDate: todayString,
|
||||
estimatedCost: nil,
|
||||
|
||||
@@ -261,6 +261,7 @@ struct TaskCard: View {
|
||||
inProgress: false,
|
||||
frequencyId: 1,
|
||||
frequency: TaskFrequency(id: 1, name: "monthly", days: 30, displayOrder: 0),
|
||||
customIntervalDays: nil,
|
||||
dueDate: "2024-12-15",
|
||||
nextDueDate: nil,
|
||||
estimatedCost: 150.00,
|
||||
|
||||
@@ -95,6 +95,7 @@ struct TasksSection: View {
|
||||
inProgress: false,
|
||||
frequencyId: 1,
|
||||
frequency: TaskFrequency(id: 1, name: "monthly", days: 30, displayOrder: 0),
|
||||
customIntervalDays: nil,
|
||||
dueDate: "2024-12-15",
|
||||
nextDueDate: nil,
|
||||
estimatedCost: 150.00,
|
||||
@@ -135,6 +136,7 @@ struct TasksSection: View {
|
||||
inProgress: false,
|
||||
frequencyId: 6,
|
||||
frequency: TaskFrequency(id: 6, name: "once", days: nil, displayOrder: 0),
|
||||
customIntervalDays: nil,
|
||||
dueDate: "2024-11-01",
|
||||
nextDueDate: nil,
|
||||
estimatedCost: 200.00,
|
||||
|
||||
@@ -71,7 +71,7 @@ struct TaskFormView: View {
|
||||
formatter.dateFormat = "yyyy-MM-dd"
|
||||
_dueDate = State(initialValue: formatter.date(from: task.effectiveDueDate ?? "") ?? Date())
|
||||
|
||||
_intervalDays = State(initialValue: "") // No longer in API
|
||||
_intervalDays = State(initialValue: task.customIntervalDays != nil ? String(task.customIntervalDays!.int32Value) : "")
|
||||
_estimatedCost = State(initialValue: task.estimatedCost != nil ? String(task.estimatedCost!.doubleValue) : "")
|
||||
} else {
|
||||
_title = State(initialValue: "")
|
||||
@@ -220,8 +220,15 @@ struct TaskFormView: View {
|
||||
Text(frequency.displayName).tag(frequency as TaskFrequency?)
|
||||
}
|
||||
}
|
||||
.onChange(of: selectedFrequency) { newFrequency in
|
||||
// Clear interval days if not Custom frequency
|
||||
if newFrequency?.name.lowercased() != "custom" {
|
||||
intervalDays = ""
|
||||
}
|
||||
}
|
||||
|
||||
if selectedFrequency?.name != "once" {
|
||||
// Show custom interval field only for "Custom" frequency
|
||||
if selectedFrequency?.name.lowercased() == "custom" {
|
||||
TextField(L10n.Tasks.customInterval, text: $intervalDays)
|
||||
.keyboardType(.numberPad)
|
||||
.focused($focusedField, equals: .intervalDays)
|
||||
@@ -231,9 +238,15 @@ struct TaskFormView: View {
|
||||
} header: {
|
||||
Text(L10n.Tasks.scheduling)
|
||||
} footer: {
|
||||
Text(L10n.Tasks.required)
|
||||
.font(.caption)
|
||||
.foregroundColor(Color.appError)
|
||||
if selectedFrequency?.name.lowercased() == "custom" {
|
||||
Text("Enter the number of days between each occurrence")
|
||||
.font(.caption)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
} else {
|
||||
Text(L10n.Tasks.required)
|
||||
.font(.caption)
|
||||
.foregroundColor(Color.appError)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.appBackgroundSecondary)
|
||||
|
||||
@@ -460,6 +473,11 @@ struct TaskFormView: View {
|
||||
|
||||
if isEditMode, let task = existingTask {
|
||||
// UPDATE existing task
|
||||
// Include customIntervalDays only for "Custom" frequency
|
||||
let customInterval: KotlinInt? = frequency.name.lowercased() == "custom" && !intervalDays.isEmpty
|
||||
? KotlinInt(int: Int32(intervalDays) ?? 0)
|
||||
: nil
|
||||
|
||||
let request = TaskCreateRequest(
|
||||
residenceId: task.residenceId,
|
||||
title: title,
|
||||
@@ -468,6 +486,7 @@ struct TaskFormView: View {
|
||||
priorityId: KotlinInt(int: Int32(priority.id)),
|
||||
inProgress: inProgress,
|
||||
frequencyId: KotlinInt(int: Int32(frequency.id)),
|
||||
customIntervalDays: customInterval,
|
||||
assignedToId: nil,
|
||||
dueDate: dueDateString,
|
||||
estimatedCost: estimatedCost.isEmpty ? nil : KotlinDouble(double: Double(estimatedCost) ?? 0.0),
|
||||
@@ -491,6 +510,11 @@ struct TaskFormView: View {
|
||||
return
|
||||
}
|
||||
|
||||
// Include customIntervalDays only for "Custom" frequency
|
||||
let customIntervalCreate: KotlinInt? = frequency.name.lowercased() == "custom" && !intervalDays.isEmpty
|
||||
? KotlinInt(int: Int32(intervalDays) ?? 0)
|
||||
: nil
|
||||
|
||||
let request = TaskCreateRequest(
|
||||
residenceId: actualResidenceId,
|
||||
title: title,
|
||||
@@ -499,6 +523,7 @@ struct TaskFormView: View {
|
||||
priorityId: KotlinInt(int: Int32(priority.id)),
|
||||
inProgress: inProgress,
|
||||
frequencyId: KotlinInt(int: Int32(frequency.id)),
|
||||
customIntervalDays: customIntervalCreate,
|
||||
assignedToId: nil,
|
||||
dueDate: dueDateString,
|
||||
estimatedCost: estimatedCost.isEmpty ? nil : KotlinDouble(double: Double(estimatedCost) ?? 0.0),
|
||||
|
||||
Reference in New Issue
Block a user