feat(tasks): вложенные задачи (подзадачи) + per-task меню «⋯»
Паритет веб-версии Задач: - CalDAV: RELATED-TO;RELTYPE=PARENT в модели/парсере/билдере VTODO (DavTask.parentUid); createTask принимает parentUid; updateTask и toggleComplete сохраняют связь с родителем. - TasksRepository/VM: createSubtask(parent), deleteTask(task) для любой задачи (не только открытой детали). - UI: у задачи меню «⋯» → «Добавить вложенную задачу» / «Удалить задачу»; подзадачи рисуются под родителем с отступом 24dp; диалог ввода названия подзадачи. Подзадачи исключены из верхнего уровня (не дублируются). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -75,6 +75,8 @@ data class DavTask(
|
||||
val description: String,
|
||||
val calendarName: String,
|
||||
val calendarHref: String,
|
||||
/** UID родительской задачи (RELATED-TO;RELTYPE=PARENT) — для подзадач; пусто у корневых. */
|
||||
val parentUid: String = "",
|
||||
) {
|
||||
val isCompleted: Boolean
|
||||
get() = status.equals("COMPLETED", ignoreCase = true) || percentComplete >= 100
|
||||
@@ -87,6 +89,11 @@ object CalDavClient {
|
||||
private val icsDue = Pattern.compile("DUE[^:]*:([^\\r\\n]+)", Pattern.CASE_INSENSITIVE)
|
||||
private val icsStatus = Pattern.compile("STATUS:([^\\r\\n]+)", Pattern.CASE_INSENSITIVE)
|
||||
private val icsUid = Pattern.compile("UID:([^\\r\\n]+)", Pattern.CASE_INSENSITIVE)
|
||||
// RELATED-TO с необязательным RELTYPE=PARENT (или без параметров) → UID родителя.
|
||||
private val icsRelatedTo = Pattern.compile(
|
||||
"RELATED-TO(?:;[^:\\r\\n]*)?:([^\\r\\n]+)",
|
||||
Pattern.CASE_INSENSITIVE,
|
||||
)
|
||||
private val icsDtStamp = Pattern.compile("DTSTAMP[^:]*:([^\\r\\n]+)", Pattern.CASE_INSENSITIVE)
|
||||
private val icsLastModified = Pattern.compile("LAST-MODIFIED[^:]*:([^\\r\\n]+)", Pattern.CASE_INSENSITIVE)
|
||||
private val icsPriority = Pattern.compile("PRIORITY:([^\\r\\n]+)", Pattern.CASE_INSENSITIVE)
|
||||
@@ -634,6 +641,7 @@ object CalDavClient {
|
||||
dueDate: LocalDate? = null,
|
||||
priority: Int = 0,
|
||||
description: String = "",
|
||||
parentUid: String = "",
|
||||
): DavTask {
|
||||
val uid = "${UUID.randomUUID()}@f7cloud.mobile"
|
||||
val ics = buildVTodoIcs(
|
||||
@@ -644,6 +652,7 @@ object CalDavClient {
|
||||
priority = priority,
|
||||
percentComplete = 0,
|
||||
description = description,
|
||||
parentUid = parentUid,
|
||||
)
|
||||
val url = calendar.href.trimEnd('/') + "/$uid.ics"
|
||||
val req = Request.Builder()
|
||||
@@ -670,6 +679,7 @@ object CalDavClient {
|
||||
description = description,
|
||||
calendarName = calendar.displayName,
|
||||
calendarHref = calendar.href,
|
||||
parentUid = parentUid,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -691,6 +701,7 @@ object CalDavClient {
|
||||
priority = priority,
|
||||
percentComplete = percentComplete,
|
||||
description = description,
|
||||
parentUid = task.parentUid, // не терять связь с родителем при правке подзадачи
|
||||
)
|
||||
val req = Request.Builder()
|
||||
.url(task.href)
|
||||
@@ -938,6 +949,7 @@ object CalDavClient {
|
||||
description = extractIcs(icsDescription, ics),
|
||||
calendarName = calendarName,
|
||||
calendarHref = calendarHref,
|
||||
parentUid = extractIcs(icsRelatedTo, ics).trim(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -949,6 +961,7 @@ object CalDavClient {
|
||||
priority: Int,
|
||||
percentComplete: Int,
|
||||
description: String,
|
||||
parentUid: String = "",
|
||||
): String {
|
||||
val now = Instant.now()
|
||||
return buildString {
|
||||
@@ -976,6 +989,9 @@ object CalDavClient {
|
||||
if (description.isNotBlank()) {
|
||||
appendLine("DESCRIPTION:${escapeIcsText(description)}")
|
||||
}
|
||||
if (parentUid.isNotBlank()) {
|
||||
appendLine("RELATED-TO;RELTYPE=PARENT:${escapeIcsText(parentUid)}")
|
||||
}
|
||||
appendLine("END:VTODO")
|
||||
appendLine("END:VCALENDAR")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user