Skip to content

Commit

Permalink
Merge pull request #60 from opatry/task-list-sorting
Browse files Browse the repository at this point in the history
Task list sorting
  • Loading branch information
opatry authored Oct 15, 2024
2 parents b6156b2 + 84ee009 commit 659c14d
Show file tree
Hide file tree
Showing 15 changed files with 534 additions and 196 deletions.
194 changes: 194 additions & 0 deletions tasks-app-shared/schemas/net.opatry.tasks.data.TasksAppDatabase/3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "17de8549f80d34ddcf3b7baff29a9f31",
"entities": [
{
"tableName": "task_list",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `remote_id` TEXT, `etag` TEXT NOT NULL DEFAULT '', `title` TEXT NOT NULL, `update_date` TEXT NOT NULL, `sorting` TEXT NOT NULL DEFAULT 'UserDefined')",
"fields": [
{
"fieldPath": "id",
"columnName": "local_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "remoteId",
"columnName": "remote_id",
"affinity": "TEXT"
},
{
"fieldPath": "etag",
"columnName": "etag",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastUpdateDate",
"columnName": "update_date",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sorting",
"columnName": "sorting",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'UserDefined'"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"local_id"
]
}
},
{
"tableName": "task",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `remote_id` TEXT, `parent_list_local_id` INTEGER NOT NULL, `etag` TEXT NOT NULL DEFAULT '', `title` TEXT NOT NULL, `due_date` TEXT, `update_date` TEXT NOT NULL, `completion_date` TEXT, `notes` TEXT NOT NULL DEFAULT '', `is_completed` INTEGER NOT NULL, `position` TEXT NOT NULL, `parent_local_id` INTEGER, `remote_parent_id` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "local_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "remoteId",
"columnName": "remote_id",
"affinity": "TEXT"
},
{
"fieldPath": "parentListLocalId",
"columnName": "parent_list_local_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "etag",
"columnName": "etag",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "dueDate",
"columnName": "due_date",
"affinity": "TEXT"
},
{
"fieldPath": "lastUpdateDate",
"columnName": "update_date",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "completionDate",
"columnName": "completion_date",
"affinity": "TEXT"
},
{
"fieldPath": "notes",
"columnName": "notes",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "isCompleted",
"columnName": "is_completed",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "position",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "parentTaskLocalId",
"columnName": "parent_local_id",
"affinity": "INTEGER"
},
{
"fieldPath": "parentTaskRemoteId",
"columnName": "remote_parent_id",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"local_id"
]
}
},
{
"tableName": "user",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `remote_id` TEXT, `name` TEXT NOT NULL, `email` TEXT, `avatar_url` TEXT, `is_signed_in` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "remoteId",
"columnName": "remote_id",
"affinity": "TEXT"
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "email",
"columnName": "email",
"affinity": "TEXT"
},
{
"fieldPath": "avatarUrl",
"columnName": "avatar_url",
"affinity": "TEXT"
},
{
"fieldPath": "isSignedIn",
"columnName": "is_signed_in",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '17de8549f80d34ddcf3b7baff29a9f31')"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
<string name="task_due_date_label_yesterday">Hier</string>
<string name="task_due_date_label_today">Aujourd'hui</string>
<string name="task_due_date_label_tomorrow">Demain</string>
<string name="task_due_date_label_past">Passée</string>
<string name="task_due_date_label_no_date">Sans date</string>
<string name="task_due_date_update_cta">Mettre à jour</string>

<string name="task_editor_sheet_edit_title">Modifier la tâche</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
<string name="task_due_date_label_yesterday">Yesterday</string>
<string name="task_due_date_label_today">Today</string>
<string name="task_due_date_label_tomorrow">Tomorrow</string>
<string name="task_due_date_label_past">Past</string>
<string name="task_due_date_label_no_date">No date</string>
<string name="task_due_date_update_cta">Update</string>

<string name="task_editor_sheet_edit_title">Edit task</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,41 @@ import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.atStartOfDayIn
import kotlinx.datetime.toLocalDateTime
import net.opatry.tasks.app.ui.model.DateRange
import net.opatry.tasks.app.ui.model.TaskListUIModel
import net.opatry.tasks.app.ui.model.TaskUIModel
import net.opatry.tasks.app.ui.model.compareTo
import net.opatry.tasks.data.TaskListSorting
import net.opatry.tasks.data.TaskRepository
import net.opatry.tasks.data.model.TaskDataModel
import net.opatry.tasks.data.model.TaskListDataModel
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

private fun TaskListDataModel.asTaskListUIModel(): TaskListUIModel {
// TODO children
// TODO date formatter
val (completedTasks, remainingTasks) = tasks.map(TaskDataModel::asTaskUIModel).partition(TaskUIModel::isCompleted)

val taskGroups = when (sorting) {
// no grouping
TaskListSorting.Manual -> mapOf(null to remainingTasks)
TaskListSorting.DueDate -> remainingTasks
.sortedWith { o1, o2 -> o1.dateRange.compareTo(o2.dateRange) }
.groupBy { task ->
when (task.dateRange) {
// merge all overdue tasks to the same range
is DateRange.Overdue -> DateRange.Overdue(LocalDate.fromEpochDays(-1), 1)
else -> task.dateRange
}
}
}

return TaskListUIModel(
id = id,
title = title,
lastUpdate = lastUpdate.toString(),
tasks = tasks.map(TaskDataModel::asTaskUIModel)
remainingTasks = taskGroups.toMap(),
completedTasks = completedTasks,
sorting = sorting,
)
}

Expand Down Expand Up @@ -141,6 +160,17 @@ class TaskListsViewModel(
}
}

fun sortBy(taskList: TaskListUIModel, sorting: TaskListSorting) {
viewModelScope.launch {
try {
taskRepository.sortTasksBy(taskList.id, sorting)
} catch (e: Exception) {
println("Error while sorting task list: $e")
// TODO error handling
}
}
}

fun createTask(taskList: TaskListUIModel, title: String, notes: String = "", dueDate: LocalDate? = null) {
viewModelScope.launch {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import androidx.compose.ui.unit.dp
import net.opatry.tasks.app.ui.model.TaskListUIModel
import net.opatry.tasks.app.ui.tooling.TaskfolioPreview
import net.opatry.tasks.app.ui.tooling.TaskfolioThemedPreview
import net.opatry.tasks.data.TaskListSorting
import net.opatry.tasks.resources.Res
import net.opatry.tasks.resources.task_list_menu_clear_all_completed_tasks
import net.opatry.tasks.resources.task_list_menu_default_list_cannot_be_deleted
Expand All @@ -71,7 +72,11 @@ enum class TaskListMenuAction {
}

@Composable
fun TaskListMenu(taskList: TaskListUIModel, expanded: Boolean, onAction: (TaskListMenuAction) -> Unit) {
fun TaskListMenu(
taskList: TaskListUIModel,
expanded: Boolean,
onAction: (TaskListMenuAction) -> Unit
) {
val allowDelete by remember(taskList.canDelete) { mutableStateOf(taskList.canDelete) }

DropdownMenu(
Expand All @@ -90,19 +95,19 @@ fun TaskListMenu(taskList: TaskListUIModel, expanded: Boolean, onAction: (TaskLi
text = {
RowWithIcon(
stringResource(Res.string.task_list_menu_sort_manual),
LucideIcons.Check.takeIf { false/*taskList.sorting == TaskListSorting.Manual*/ })
LucideIcons.Check.takeIf { taskList.sorting == TaskListSorting.Manual })
},
enabled = false, // TODO enable when sorting is implemented
enabled = taskList.sorting != TaskListSorting.Manual,
onClick = { onAction(TaskListMenuAction.SortManual) }
)

DropdownMenuItem(
text = {
RowWithIcon(
stringResource(Res.string.task_list_menu_sort_due_date),
LucideIcons.Check.takeIf { false/*taskList.sorting == TaskListSorting.Date*/ })
LucideIcons.Check.takeIf { taskList.sorting == TaskListSorting.DueDate })
},
enabled = false, // TODO enable when sorting is implemented
enabled = taskList.sorting != TaskListSorting.DueDate,
onClick = { onAction(TaskListMenuAction.SortDate) }
)

Expand Down Expand Up @@ -161,7 +166,7 @@ private fun TaskListMenuPreview() {
) {
IconButton(onClick = { showMenu = true }) {
Icon(LucideIcons.EllipsisVertical, null)
TaskListMenu(TaskListUIModel(0L, "My task list", "TODO DATE", tasks = emptyList()), showMenu) {}
TaskListMenu(TaskListUIModel(0L, "My task list", "TODO DATE"), showMenu) {}
}
}
}
Expand Down
Loading

0 comments on commit 659c14d

Please sign in to comment.