feat(todo): filters in Todoist's language, and a view of each label (ADR 0033, D35)
A filter is a query read on the device, as the server sees no task, kept sealed in `saved_filter`. Filters & labels is a screen of its own under Browse; a label opens its tasks, and favourite filters and labels join Browse's Favourites. D36 records what of Todoist's Pro stays out. Whats-New: Filters: keep views like "today & p1", and open a label to see its tasks, under Browse in Filters & labels. Whats-New-De: Filter: Speichere Ansichten wie „heute & p1“ und öffne ein Label, um seine Aufgaben zu sehen, in der Übersicht unter Filter & Labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
a4c1fe298d
commit
b1f2078357
47 changed files with 21688 additions and 71 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,613 @@
|
|||
{
|
||||
"__className__": "serverpod.DatabaseDefinition",
|
||||
"schemaVersion": 2,
|
||||
"moduleName": "halkyon_todo",
|
||||
"tables": [
|
||||
{
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "content_key",
|
||||
"dartName": "ContentKey",
|
||||
"module": "halkyon_todo",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "spaceId",
|
||||
"fieldName": "spaceId",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "dataKeyId",
|
||||
"fieldName": "dataKeyId",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "sealedKey",
|
||||
"fieldName": "sealedKey",
|
||||
"columnType": 5,
|
||||
"isNullable": false,
|
||||
"dartType": "dart:typed_data:ByteData"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "content_key_fk_0",
|
||||
"columns": [
|
||||
"spaceId"
|
||||
],
|
||||
"referenceTable": "offline_sync_spaces",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "content_key_space_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "spaceId"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "label",
|
||||
"dartName": "Label",
|
||||
"module": "halkyon_todo",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "spaceId",
|
||||
"fieldName": "spaceId",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "keyId",
|
||||
"fieldName": "keyId",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "payload",
|
||||
"fieldName": "payload",
|
||||
"columnType": 5,
|
||||
"isNullable": false,
|
||||
"dartType": "dart:typed_data:ByteData"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "nonce",
|
||||
"fieldName": "nonce",
|
||||
"columnType": 5,
|
||||
"isNullable": true,
|
||||
"dartType": "dart:typed_data:ByteData?"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "label_fk_0",
|
||||
"columns": [
|
||||
"spaceId"
|
||||
],
|
||||
"referenceTable": "offline_sync_spaces",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "label_space_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "spaceId"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "project",
|
||||
"dartName": "Project",
|
||||
"module": "halkyon_todo",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "spaceId",
|
||||
"fieldName": "spaceId",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "keyId",
|
||||
"fieldName": "keyId",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "payload",
|
||||
"fieldName": "payload",
|
||||
"columnType": 5,
|
||||
"isNullable": false,
|
||||
"dartType": "dart:typed_data:ByteData"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "nonce",
|
||||
"fieldName": "nonce",
|
||||
"columnType": 5,
|
||||
"isNullable": true,
|
||||
"dartType": "dart:typed_data:ByteData?"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "project_fk_0",
|
||||
"columns": [
|
||||
"spaceId"
|
||||
],
|
||||
"referenceTable": "offline_sync_spaces",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "project_space_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "spaceId"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "saved_filter",
|
||||
"dartName": "SavedFilter",
|
||||
"module": "halkyon_todo",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "spaceId",
|
||||
"fieldName": "spaceId",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "keyId",
|
||||
"fieldName": "keyId",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "payload",
|
||||
"fieldName": "payload",
|
||||
"columnType": 5,
|
||||
"isNullable": false,
|
||||
"dartType": "dart:typed_data:ByteData"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "nonce",
|
||||
"fieldName": "nonce",
|
||||
"columnType": 5,
|
||||
"isNullable": true,
|
||||
"dartType": "dart:typed_data:ByteData?"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "saved_filter_fk_0",
|
||||
"columns": [
|
||||
"spaceId"
|
||||
],
|
||||
"referenceTable": "offline_sync_spaces",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "saved_filter_space_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "spaceId"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "section",
|
||||
"dartName": "Section",
|
||||
"module": "halkyon_todo",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "spaceId",
|
||||
"fieldName": "spaceId",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "keyId",
|
||||
"fieldName": "keyId",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "payload",
|
||||
"fieldName": "payload",
|
||||
"columnType": 5,
|
||||
"isNullable": false,
|
||||
"dartType": "dart:typed_data:ByteData"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "nonce",
|
||||
"fieldName": "nonce",
|
||||
"columnType": 5,
|
||||
"isNullable": true,
|
||||
"dartType": "dart:typed_data:ByteData?"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "section_fk_0",
|
||||
"columns": [
|
||||
"spaceId"
|
||||
],
|
||||
"referenceTable": "offline_sync_spaces",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "section_space_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "spaceId"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "task",
|
||||
"dartName": "Task",
|
||||
"module": "halkyon_todo",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "spaceId",
|
||||
"fieldName": "spaceId",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "keyId",
|
||||
"fieldName": "keyId",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "payload",
|
||||
"fieldName": "payload",
|
||||
"columnType": 5,
|
||||
"isNullable": false,
|
||||
"dartType": "dart:typed_data:ByteData"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "nonce",
|
||||
"fieldName": "nonce",
|
||||
"columnType": 5,
|
||||
"isNullable": true,
|
||||
"dartType": "dart:typed_data:ByteData?"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "task_fk_0",
|
||||
"columns": [
|
||||
"spaceId"
|
||||
],
|
||||
"referenceTable": "offline_sync_spaces",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "task_space_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "spaceId"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "todo_settings",
|
||||
"dartName": "TodoSettings",
|
||||
"module": "halkyon_todo",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "spaceId",
|
||||
"fieldName": "spaceId",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "keyId",
|
||||
"fieldName": "keyId",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "payload",
|
||||
"fieldName": "payload",
|
||||
"columnType": 5,
|
||||
"isNullable": false,
|
||||
"dartType": "dart:typed_data:ByteData"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "nonce",
|
||||
"fieldName": "nonce",
|
||||
"columnType": 5,
|
||||
"isNullable": true,
|
||||
"dartType": "dart:typed_data:ByteData?"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "todo_settings_fk_0",
|
||||
"columns": [
|
||||
"spaceId"
|
||||
],
|
||||
"referenceTable": "offline_sync_spaces",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "todo_settings_space_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "spaceId"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
],
|
||||
"installedModules": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationVersion",
|
||||
"module": "halkyon_base",
|
||||
"version": "20261026140329453-assistant-budgets"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationVersion",
|
||||
"module": "serverpod",
|
||||
"version": "20260824182259319"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationVersion",
|
||||
"module": "serverpod_auth_idp",
|
||||
"version": "20260924105404509"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationVersion",
|
||||
"module": "serverpod_offline_sync",
|
||||
"version": "20260914143806119"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationVersion",
|
||||
"module": "halkyon_oidc",
|
||||
"version": "20261023180644988"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationVersion",
|
||||
"module": "serverpod_auth_core",
|
||||
"version": "20260924105232991"
|
||||
}
|
||||
],
|
||||
"migrationApiVersion": 1
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"__className__": "serverpod.DatabaseMigration",
|
||||
"actions": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "saved_filter",
|
||||
"dartName": "SavedFilter",
|
||||
"module": "halkyon_todo",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "spaceId",
|
||||
"fieldName": "spaceId",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "keyId",
|
||||
"fieldName": "keyId",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "payload",
|
||||
"fieldName": "payload",
|
||||
"columnType": 5,
|
||||
"isNullable": false,
|
||||
"dartType": "dart:typed_data:ByteData"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "nonce",
|
||||
"fieldName": "nonce",
|
||||
"columnType": 5,
|
||||
"isNullable": true,
|
||||
"dartType": "dart:typed_data:ByteData?"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "saved_filter_fk_0",
|
||||
"columns": [
|
||||
"spaceId"
|
||||
],
|
||||
"referenceTable": "offline_sync_spaces",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "saved_filter_space_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "spaceId"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"warnings": [],
|
||||
"migrationApiVersion": 1
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import 'package:serverpod_database/serverpod_database.dart';
|
|||
part '20261101000019144-projects/migration.dart';
|
||||
part '20261101000604776-tasks/migration.dart';
|
||||
part '20261101180755366-settings/migration.dart';
|
||||
part '20261106183802735-filters/migration.dart';
|
||||
|
||||
/// Migration registry for the client-side database.
|
||||
class MigrationRegistry {
|
||||
|
|
@ -16,6 +17,7 @@ class MigrationRegistry {
|
|||
_Migration20261101000019144_projects(),
|
||||
_Migration20261101000604776_tasks(),
|
||||
_Migration20261101180755366_settings(),
|
||||
_Migration20261106183802735_filters(),
|
||||
];
|
||||
|
||||
/// List of all client-side database migration versions.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,672 @@
|
|||
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||
/* To generate run: "serverpod generate" */
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: type_literal_in_constant_pattern
|
||||
// ignore_for_file: use_super_parameters
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:typed_data' as _idt;
|
||||
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||
import 'package:serverpod_database/serverpod_database.dart' as _isd;
|
||||
|
||||
/// A saved filter (ADR 0033, D35): its name, its query and how it shows, in
|
||||
/// its sealed payload (D5); the instance sees that it exists, its size and
|
||||
/// when it changed, and never runs it: tasks are matched on the device.
|
||||
/// Changed when edited, the last writer winning. Synced.
|
||||
abstract class SavedFilter
|
||||
implements _isd.TableRow<_isc.UuidValue?>, _isc.ProtocolSerialization {
|
||||
SavedFilter._({
|
||||
this.id,
|
||||
this.spaceId,
|
||||
this.keyId,
|
||||
required this.payload,
|
||||
this.nonce,
|
||||
});
|
||||
|
||||
factory SavedFilter({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
required _idt.ByteData payload,
|
||||
_idt.ByteData? nonce,
|
||||
}) = _SavedFilterImpl;
|
||||
|
||||
factory SavedFilter.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return SavedFilter(
|
||||
id: jsonSerialization['id'] == null
|
||||
? null
|
||||
: _isc.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
|
||||
spaceId: jsonSerialization['spaceId'] as int?,
|
||||
keyId: jsonSerialization['keyId'] as String?,
|
||||
payload: _isc.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['payload'],
|
||||
),
|
||||
nonce: jsonSerialization['nonce'] == null
|
||||
? null
|
||||
: _isc.ByteDataJsonExtension.fromJson(jsonSerialization['nonce']),
|
||||
);
|
||||
}
|
||||
|
||||
static final t = SavedFilterTable();
|
||||
|
||||
static const db = SavedFilterRepository._();
|
||||
|
||||
@override
|
||||
_isc.UuidValue? id;
|
||||
|
||||
/// The space owning this row. Maintained by the sync engine.
|
||||
int? spaceId;
|
||||
|
||||
/// The key it is sealed with (ADR 0002's envelope); never null in Todo,
|
||||
/// where everything is sealed.
|
||||
String? keyId;
|
||||
|
||||
/// The content, sealed: UTF-8 JSON with a `schemaVersion` once opened.
|
||||
_idt.ByteData payload;
|
||||
|
||||
/// The sealing's nonce (ADR 0002's envelope).
|
||||
_idt.ByteData? nonce;
|
||||
|
||||
@override
|
||||
_isd.Table<_isc.UuidValue?> get table => t;
|
||||
|
||||
/// Returns a shallow copy of this [SavedFilter]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
SavedFilter copyWith({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
_idt.ByteData? payload,
|
||||
_idt.ByteData? nonce,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'SavedFilter',
|
||||
if (id != null) 'id': id?.toJson(),
|
||||
if (spaceId != null) 'spaceId': spaceId,
|
||||
if (keyId != null) 'keyId': keyId,
|
||||
'payload': payload.toJson(),
|
||||
if (nonce != null) 'nonce': nonce?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'SavedFilter',
|
||||
if (id != null) 'id': id?.toJson(),
|
||||
if (spaceId != null) 'spaceId': spaceId,
|
||||
if (keyId != null) 'keyId': keyId,
|
||||
'payload': payload.toJson(),
|
||||
if (nonce != null) 'nonce': nonce?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
static SavedFilterInclude include() {
|
||||
return SavedFilterInclude._();
|
||||
}
|
||||
|
||||
static SavedFilterIncludeList includeList({
|
||||
_isd.WhereExpressionBuilder<SavedFilterTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<SavedFilterTable>? orderBy,
|
||||
_isd.OrderByListBuilder<SavedFilterTable>? orderByList,
|
||||
SavedFilterInclude? include,
|
||||
}) {
|
||||
return SavedFilterIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(SavedFilter.t),
|
||||
orderByList: orderByList?.call(SavedFilter.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _SavedFilterImpl extends SavedFilter {
|
||||
_SavedFilterImpl({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
required _idt.ByteData payload,
|
||||
_idt.ByteData? nonce,
|
||||
}) : super._(
|
||||
id: id,
|
||||
spaceId: spaceId,
|
||||
keyId: keyId,
|
||||
payload: payload,
|
||||
nonce: nonce,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [SavedFilter]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
SavedFilter copyWith({
|
||||
Object? id = _Undefined,
|
||||
Object? spaceId = _Undefined,
|
||||
Object? keyId = _Undefined,
|
||||
_idt.ByteData? payload,
|
||||
Object? nonce = _Undefined,
|
||||
}) {
|
||||
return SavedFilter(
|
||||
id: id is _isc.UuidValue? ? id : this.id,
|
||||
spaceId: spaceId is int? ? spaceId : this.spaceId,
|
||||
keyId: keyId is String? ? keyId : this.keyId,
|
||||
payload: payload ?? this.payload.clone(),
|
||||
nonce: nonce is _idt.ByteData? ? nonce : this.nonce?.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SavedFilterUpdateTable extends _isd.UpdateTable<SavedFilterTable> {
|
||||
SavedFilterUpdateTable(super.table);
|
||||
|
||||
_isd.ColumnValue<int, int> spaceId(int? value) => _isd.ColumnValue(
|
||||
table.spaceId,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<String, String> keyId(String? value) => _isd.ColumnValue(
|
||||
table.keyId,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<_idt.ByteData, _idt.ByteData> payload(_idt.ByteData value) =>
|
||||
_isd.ColumnValue(
|
||||
table.payload,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<_idt.ByteData, _idt.ByteData> nonce(_idt.ByteData? value) =>
|
||||
_isd.ColumnValue(
|
||||
table.nonce,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class SavedFilterTable extends _isd.Table<_isc.UuidValue?> {
|
||||
SavedFilterTable({super.tableRelation}) : super(tableName: 'saved_filter') {
|
||||
updateTable = SavedFilterUpdateTable(this);
|
||||
spaceId = _isd.ColumnInt(
|
||||
'spaceId',
|
||||
this,
|
||||
);
|
||||
keyId = _isd.ColumnString(
|
||||
'keyId',
|
||||
this,
|
||||
);
|
||||
payload = _isd.ColumnByteData(
|
||||
'payload',
|
||||
this,
|
||||
);
|
||||
nonce = _isd.ColumnByteData(
|
||||
'nonce',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final SavedFilterUpdateTable updateTable;
|
||||
|
||||
/// The space owning this row. Maintained by the sync engine.
|
||||
late final _isd.ColumnInt spaceId;
|
||||
|
||||
/// The key it is sealed with (ADR 0002's envelope); never null in Todo,
|
||||
/// where everything is sealed.
|
||||
late final _isd.ColumnString keyId;
|
||||
|
||||
/// The content, sealed: UTF-8 JSON with a `schemaVersion` once opened.
|
||||
late final _isd.ColumnByteData payload;
|
||||
|
||||
/// The sealing's nonce (ADR 0002's envelope).
|
||||
late final _isd.ColumnByteData nonce;
|
||||
|
||||
@override
|
||||
List<_isd.Column> get columns => [
|
||||
id,
|
||||
spaceId,
|
||||
keyId,
|
||||
payload,
|
||||
nonce,
|
||||
];
|
||||
}
|
||||
|
||||
class SavedFilterInclude extends _isd.IncludeObject {
|
||||
SavedFilterInclude._();
|
||||
|
||||
@override
|
||||
Map<String, _isd.Include?> get includes => {};
|
||||
|
||||
@override
|
||||
_isd.Table<_isc.UuidValue?> get table => SavedFilter.t;
|
||||
}
|
||||
|
||||
class SavedFilterIncludeList extends _isd.IncludeList {
|
||||
SavedFilterIncludeList._({
|
||||
_isd.WhereExpressionBuilder<SavedFilterTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(SavedFilter.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _isd.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_isd.Table<_isc.UuidValue?> get table => SavedFilter.t;
|
||||
}
|
||||
|
||||
class SavedFilterRepository {
|
||||
const SavedFilterRepository._();
|
||||
|
||||
/// Returns a list of [SavedFilter]s matching the given query parameters.
|
||||
///
|
||||
/// Use [where] to specify which items to include in the return value.
|
||||
/// If none is specified, all items will be returned.
|
||||
///
|
||||
/// To specify the order of the items use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// The maximum number of items can be set by [limit]. If no limit is set,
|
||||
/// all items matching the query will be returned.
|
||||
///
|
||||
/// [offset] defines how many items to skip, after which [limit] (or all)
|
||||
/// items are read from the database.
|
||||
///
|
||||
/// ```dart
|
||||
/// var persons = await Persons.db.find(
|
||||
/// session,
|
||||
/// where: (t) => t.lastName.equals('Jones'),
|
||||
/// orderBy: (t) => t.firstName,
|
||||
/// limit: 100,
|
||||
/// );
|
||||
/// ```
|
||||
Future<List<SavedFilter>> find(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<SavedFilterTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<SavedFilterTable>? orderBy,
|
||||
_isd.OrderByListBuilder<SavedFilterTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<SavedFilter>(
|
||||
where: where?.call(SavedFilter.t),
|
||||
orderBy: orderBy?.call(SavedFilter.t),
|
||||
orderByList: orderByList?.call(SavedFilter.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [SavedFilter] matching the given query parameters.
|
||||
///
|
||||
/// Use [where] to specify which items to include in the return value.
|
||||
/// If none is specified, all items will be returned.
|
||||
///
|
||||
/// To specify the order use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// [offset] defines how many items to skip, after which the next one will be picked.
|
||||
///
|
||||
/// ```dart
|
||||
/// var youngestPerson = await Persons.db.findFirstRow(
|
||||
/// session,
|
||||
/// where: (t) => t.lastName.equals('Jones'),
|
||||
/// orderBy: (t) => t.age,
|
||||
/// );
|
||||
/// ```
|
||||
Future<SavedFilter?> findFirstRow(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<SavedFilterTable>? where,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<SavedFilterTable>? orderBy,
|
||||
_isd.OrderByListBuilder<SavedFilterTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<SavedFilter>(
|
||||
where: where?.call(SavedFilter.t),
|
||||
orderBy: orderBy?.call(SavedFilter.t),
|
||||
orderByList: orderByList?.call(SavedFilter.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [SavedFilter] by its [id] or null if no such row exists.
|
||||
Future<SavedFilter?> findById(
|
||||
_isd.DatabaseSession session,
|
||||
_isc.UuidValue id, {
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<SavedFilter>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [SavedFilter]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [SavedFilter]s will have their `id` fields set.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fails to
|
||||
/// insert, none of the rows will be inserted.
|
||||
///
|
||||
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
|
||||
/// rows are silently skipped, and only the successfully inserted rows are
|
||||
/// returned.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the inserted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<SavedFilter>> insert(
|
||||
_isd.DatabaseSession session,
|
||||
List<SavedFilter> rows, {
|
||||
_isd.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<SavedFilter>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [SavedFilter] and returns the inserted row.
|
||||
///
|
||||
/// The returned [SavedFilter] will have its `id` field set.
|
||||
Future<SavedFilter> insertRow(
|
||||
_isd.DatabaseSession session,
|
||||
SavedFilter row, {
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<SavedFilter>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [SavedFilter]s in the list and returns the resulting rows.
|
||||
///
|
||||
/// If a row conflicts on the given [conflictColumns], the existing row is
|
||||
/// updated with the new values. Otherwise, a new row is inserted.
|
||||
///
|
||||
/// If [updateColumns] is provided, only those columns will be updated on
|
||||
/// conflict. If null, all non-conflict, non-id columns are updated.
|
||||
///
|
||||
/// If [updateWhere] is provided, the update only applies to rows matching the
|
||||
/// given expression. Conflicting rows that don't match are skipped and not
|
||||
/// returned, so the resulting list may be shorter than [rows].
|
||||
///
|
||||
/// The returned [SavedFilter]s will have their `id` fields set.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fails,
|
||||
/// none of the rows will be affected.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the resulting rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<SavedFilter>> upsert(
|
||||
_isd.DatabaseSession session,
|
||||
List<SavedFilter> rows, {
|
||||
required _isd.ColumnSelections<SavedFilterTable> conflictColumns,
|
||||
_isd.ColumnSelections<SavedFilterTable>? updateColumns,
|
||||
_isd.WhereExpressionBuilder<SavedFilterTable>? updateWhere,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<SavedFilter>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(SavedFilter.t),
|
||||
updateColumns: updateColumns?.call(SavedFilter.t),
|
||||
updateWhere: updateWhere?.call(SavedFilter.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [SavedFilter] and returns the resulting row.
|
||||
///
|
||||
/// If the row conflicts on the given [conflictColumns], the existing row is
|
||||
/// updated. Otherwise, a new row is inserted.
|
||||
///
|
||||
/// If [updateColumns] is provided, only those columns will be updated on
|
||||
/// conflict. If null, all non-conflict, non-id columns are updated.
|
||||
///
|
||||
/// If [updateWhere] is provided, the update only applies when the existing
|
||||
/// row matches the expression. Returns `null` if no row was affected — for
|
||||
/// example when [updateWhere] does not match the conflicting row.
|
||||
///
|
||||
/// The returned [SavedFilter] will have its `id` field set.
|
||||
Future<SavedFilter?> upsertRow(
|
||||
_isd.DatabaseSession session,
|
||||
SavedFilter row, {
|
||||
required _isd.ColumnSelections<SavedFilterTable> conflictColumns,
|
||||
_isd.ColumnSelections<SavedFilterTable>? updateColumns,
|
||||
_isd.WhereExpressionBuilder<SavedFilterTable>? updateWhere,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<SavedFilter>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(SavedFilter.t),
|
||||
updateColumns: updateColumns?.call(SavedFilter.t),
|
||||
updateWhere: updateWhere?.call(SavedFilter.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [SavedFilter]s in the list and returns the updated rows. If
|
||||
/// [columns] is provided, only those columns will be updated. Defaults to
|
||||
/// all columns.
|
||||
/// This is an atomic operation, meaning that if one of the rows fails to
|
||||
/// update, none of the rows will be updated.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the updated rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<SavedFilter>> update(
|
||||
_isd.DatabaseSession session,
|
||||
List<SavedFilter> rows, {
|
||||
_isd.ColumnSelections<SavedFilterTable>? columns,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<SavedFilter>(
|
||||
rows,
|
||||
columns: columns?.call(SavedFilter.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [SavedFilter]. The row needs to have its id set.
|
||||
/// Optionally, a list of [columns] can be provided to only update those
|
||||
/// columns. Defaults to all columns.
|
||||
Future<SavedFilter> updateRow(
|
||||
_isd.DatabaseSession session,
|
||||
SavedFilter row, {
|
||||
_isd.ColumnSelections<SavedFilterTable>? columns,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<SavedFilter>(
|
||||
row,
|
||||
columns: columns?.call(SavedFilter.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [SavedFilter] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<SavedFilter?> updateById(
|
||||
_isd.DatabaseSession session,
|
||||
_isc.UuidValue id, {
|
||||
required _isd.ColumnValueListBuilder<SavedFilterUpdateTable> columnValues,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<SavedFilter>(
|
||||
id,
|
||||
columnValues: columnValues(SavedFilter.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [SavedFilter]s matching the [where] expression with the specified [columnValues].
|
||||
/// Returns the list of updated rows.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the updated rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<SavedFilter>> updateWhere(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.ColumnValueListBuilder<SavedFilterUpdateTable> columnValues,
|
||||
required _isd.WhereExpressionBuilder<SavedFilterTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<SavedFilterTable>? orderBy,
|
||||
_isd.OrderByListBuilder<SavedFilterTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<SavedFilter>(
|
||||
columnValues: columnValues(SavedFilter.t.updateTable),
|
||||
where: where(SavedFilter.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(SavedFilter.t),
|
||||
orderByList: orderByList?.call(SavedFilter.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [SavedFilter]s in the list and returns the deleted rows.
|
||||
///
|
||||
/// To specify the order of the returned rows use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// This is an atomic operation, meaning that if one of the rows fail to
|
||||
/// be deleted, none of the rows will be deleted.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the deleted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<SavedFilter>> delete(
|
||||
_isd.DatabaseSession session,
|
||||
List<SavedFilter> rows, {
|
||||
_isd.OrderByBuilder<SavedFilterTable>? orderBy,
|
||||
_isd.OrderByListBuilder<SavedFilterTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<SavedFilter>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(SavedFilter.t),
|
||||
orderByList: orderByList?.call(SavedFilter.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [SavedFilter].
|
||||
Future<SavedFilter> deleteRow(
|
||||
_isd.DatabaseSession session,
|
||||
SavedFilter row, {
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<SavedFilter>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all rows matching the [where] expression.
|
||||
///
|
||||
/// To specify the order of the returned rows use [orderBy] or [orderByList]
|
||||
/// when sorting by multiple columns.
|
||||
///
|
||||
/// If [noReturn] is set to `true`, the deleted rows are not read back from
|
||||
/// the database and an empty list is returned. This avoids the overhead of
|
||||
/// transferring and deserializing the rows when the result is not needed.
|
||||
Future<List<SavedFilter>> deleteWhere(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.WhereExpressionBuilder<SavedFilterTable> where,
|
||||
_isd.OrderByBuilder<SavedFilterTable>? orderBy,
|
||||
_isd.OrderByListBuilder<SavedFilterTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<SavedFilter>(
|
||||
where: where(SavedFilter.t),
|
||||
orderBy: orderBy?.call(SavedFilter.t),
|
||||
orderByList: orderByList?.call(SavedFilter.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Counts the number of rows matching the [where] expression. If omitted,
|
||||
/// will return the count of all rows in the table.
|
||||
Future<int> count(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<SavedFilterTable>? where,
|
||||
int? limit,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<SavedFilter>(
|
||||
where: where?.call(SavedFilter.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [SavedFilter] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.WhereExpressionBuilder<SavedFilterTable> where,
|
||||
required _isd.LockMode lockMode,
|
||||
required _isd.Transaction transaction,
|
||||
_isd.LockBehavior lockBehavior = _isd.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<SavedFilter>(
|
||||
where: where(SavedFilter.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
|||
import 'package:serverpod_database/serverpod_database.dart' as _isd;
|
||||
import 'package:serverpod_offline_sync_client/serverpod_offline_sync_client.dart'
|
||||
as _ipulbpi2;
|
||||
import 'filter/saved_filter.dart' as _i9q9fhid;
|
||||
import 'label/label.dart' as _ivak08z4;
|
||||
import 'privacy/content_key.dart' as _imed3x5d;
|
||||
import 'project/project.dart' as _i9c44spp;
|
||||
|
|
@ -33,6 +34,7 @@ import 'search/place_point.dart' as _itf0ur2u;
|
|||
import 'search/search_refused.dart' as _ihbxkwke;
|
||||
import 'settings/todo_settings.dart' as _icak60vd;
|
||||
import 'task/task.dart' as _ib4f8uyw;
|
||||
export 'filter/saved_filter.dart';
|
||||
export 'label/label.dart';
|
||||
export 'privacy/content_key.dart';
|
||||
export 'project/project.dart';
|
||||
|
|
@ -249,6 +251,73 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
],
|
||||
managed: true,
|
||||
),
|
||||
_isd.TableDefinition(
|
||||
name: 'saved_filter',
|
||||
dartName: 'SavedFilter',
|
||||
schema: 'public',
|
||||
module: 'halkyon_todo',
|
||||
columns: [
|
||||
_isd.ColumnDefinition(
|
||||
name: 'id',
|
||||
columnType: _isd.ColumnType.uuid,
|
||||
isNullable: false,
|
||||
dartType: 'UuidValue?',
|
||||
columnDefault: 'random_v7',
|
||||
),
|
||||
_isd.ColumnDefinition(
|
||||
name: 'spaceId',
|
||||
columnType: _isd.ColumnType.bigint,
|
||||
isNullable: true,
|
||||
dartType: 'int?',
|
||||
),
|
||||
_isd.ColumnDefinition(
|
||||
name: 'keyId',
|
||||
columnType: _isd.ColumnType.text,
|
||||
isNullable: true,
|
||||
dartType: 'String?',
|
||||
),
|
||||
_isd.ColumnDefinition(
|
||||
name: 'payload',
|
||||
columnType: _isd.ColumnType.bytea,
|
||||
isNullable: false,
|
||||
dartType: 'dart:typed_data:ByteData',
|
||||
),
|
||||
_isd.ColumnDefinition(
|
||||
name: 'nonce',
|
||||
columnType: _isd.ColumnType.bytea,
|
||||
isNullable: true,
|
||||
dartType: 'dart:typed_data:ByteData?',
|
||||
),
|
||||
],
|
||||
foreignKeys: [
|
||||
_isd.ForeignKeyDefinition(
|
||||
constraintName: 'saved_filter_fk_0',
|
||||
columns: ['spaceId'],
|
||||
referenceTable: 'offline_sync_spaces',
|
||||
referenceTableSchema: 'public',
|
||||
referenceColumns: ['id'],
|
||||
onUpdate: _isd.ForeignKeyAction.noAction,
|
||||
onDelete: _isd.ForeignKeyAction.cascade,
|
||||
matchType: null,
|
||||
),
|
||||
],
|
||||
indexes: [
|
||||
_isd.IndexDefinition(
|
||||
indexName: 'saved_filter_space_idx',
|
||||
tableSpace: null,
|
||||
elements: [
|
||||
_isd.IndexElementDefinition(
|
||||
type: _isd.IndexElementDefinitionType.column,
|
||||
definition: 'spaceId',
|
||||
),
|
||||
],
|
||||
type: 'btree',
|
||||
isUnique: false,
|
||||
isPrimary: false,
|
||||
),
|
||||
],
|
||||
managed: true,
|
||||
),
|
||||
_isd.TableDefinition(
|
||||
name: 'section',
|
||||
dartName: 'Section',
|
||||
|
|
@ -499,6 +568,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
}
|
||||
}
|
||||
|
||||
if (t == _i9q9fhid.SavedFilter) {
|
||||
return _i9q9fhid.SavedFilter.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ivak08z4.Label) {
|
||||
return _ivak08z4.Label.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -529,6 +601,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _ib4f8uyw.Task) {
|
||||
return _ib4f8uyw.Task.fromJson(data) as T;
|
||||
}
|
||||
if (t == _isc.getType<_i9q9fhid.SavedFilter?>()) {
|
||||
return (data != null ? _i9q9fhid.SavedFilter.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_ivak08z4.Label?>()) {
|
||||
return (data != null ? _ivak08z4.Label.fromJson(data) : null) as T;
|
||||
}
|
||||
|
|
@ -588,6 +663,7 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
|
||||
static String? getClassNameForType(Type type) {
|
||||
return switch (type) {
|
||||
_i9q9fhid.SavedFilter => 'SavedFilter',
|
||||
_ivak08z4.Label => 'Label',
|
||||
_imed3x5d.ContentKey => 'ContentKey',
|
||||
_i9c44spp.Project => 'Project',
|
||||
|
|
@ -615,6 +691,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
}
|
||||
|
||||
switch (data) {
|
||||
case _i9q9fhid.SavedFilter():
|
||||
return 'SavedFilter';
|
||||
case _ivak08z4.Label():
|
||||
return 'Label';
|
||||
case _imed3x5d.ContentKey():
|
||||
|
|
@ -671,6 +749,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (dataClassName is! String) {
|
||||
return super.deserializeByClassName(data);
|
||||
}
|
||||
if (dataClassName == 'SavedFilter') {
|
||||
return deserialize<_i9q9fhid.SavedFilter>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'Label') {
|
||||
return deserialize<_ivak08z4.Label>(data['data']);
|
||||
}
|
||||
|
|
@ -780,6 +861,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
}
|
||||
}
|
||||
switch (t) {
|
||||
case _i9q9fhid.SavedFilter:
|
||||
return _i9q9fhid.SavedFilter.t;
|
||||
case _ivak08z4.Label:
|
||||
return _ivak08z4.Label.t;
|
||||
case _imed3x5d.ContentKey:
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod_database/serverpod_database.dart' as _isd;
|
||||
import 'filter/saved_filter.dart' as _i9q9fhid;
|
||||
import 'label/label.dart' as _ivak08z4;
|
||||
import 'privacy/content_key.dart' as _imed3x5d;
|
||||
import 'project/project.dart' as _i9c44spp;
|
||||
|
|
@ -25,6 +26,7 @@ final List<_isd.Table> syncTables = [
|
|||
_imed3x5d.ContentKey.t,
|
||||
_ivak08z4.Label.t,
|
||||
_i9c44spp.Project.t,
|
||||
_i9q9fhid.SavedFilter.t,
|
||||
_imadkvi9.Section.t,
|
||||
_ib4f8uyw.Task.t,
|
||||
_icak60vd.TodoSettings.t,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,30 @@
|
|||
"deadlineOn": "Frist {day}",
|
||||
"deadlineNone": "Keine Frist",
|
||||
"keysMissing": "Dieses Gerät kann nichts speichern: Es hat die Schlüssel deines Kontos nicht. Melde dich mit deinem Passwort oder deiner Wiederherstellungsphrase an, um sie hinzuzufügen.",
|
||||
"filtersLabelsTitle": "Filter & Labels",
|
||||
"filtersLabelsHint": "Gespeicherte Ansichten deiner Aufgaben",
|
||||
"filtersTitle": "Meine Filter",
|
||||
"filtersNone": "Noch keine Filter.",
|
||||
"filterAdd": "Neuer Filter",
|
||||
"filterEdit": "Filter bearbeiten",
|
||||
"filterDelete": "Filter löschen",
|
||||
"filterDeleteTitle": "Filter löschen?",
|
||||
"filterName": "Name",
|
||||
"filterDescription": "Beschreibung",
|
||||
"filterQuery": "Abfrage",
|
||||
"filterQueryHint": "heute & p1",
|
||||
"filterFavorite": "Zu Favoriten hinzufügen",
|
||||
"filterUnfavorite": "Aus Favoriten entfernen",
|
||||
"filterPreviewEmpty": "Noch passt nichts.",
|
||||
"filterNothing": "Gerade sind keine Aufgaben in diesem Filter.",
|
||||
"filterHelp": "heute, morgen, überfällig, ohne datum, 7 tage, p1 bis p4, #Projekt, ##Projekt mit Unterprojekten, /Abschnitt, @label, suche: Wort. Verbinde sie mit & (und), | (oder), ! (nicht) und Klammern; * steht für Beliebiges in einem Namen.",
|
||||
"filterMenu": "Filtermenü",
|
||||
"labelMenu": "Labelmenü",
|
||||
"labelNothing": "Keine offene Aufgabe hat dieses Label.",
|
||||
"labelEditEntry": "Label bearbeiten",
|
||||
"filterMatches": "{count, plural, =1{1 Aufgabe passt} other{{count} Aufgaben passen}}",
|
||||
"filterNotRead": "Nicht verstanden bei „{near}“",
|
||||
"filterNotReadEnd": "Die Abfrage endet zu früh",
|
||||
"labelsTitle": "Labels",
|
||||
"labelsNone": "Noch keine Labels.",
|
||||
"labelsNoneSet": "Keine Labels",
|
||||
|
|
|
|||
|
|
@ -72,6 +72,49 @@
|
|||
},
|
||||
"deadlineNone": "No deadline",
|
||||
"keysMissing": "This device can't save: it doesn't hold your account's keys. Sign in with your password or recovery phrase to add them.",
|
||||
"filtersLabelsTitle": "Filters & labels",
|
||||
"filtersLabelsHint": "Views of your tasks, kept",
|
||||
"filtersTitle": "My filters",
|
||||
"filtersNone": "No filters yet.",
|
||||
"filterAdd": "New filter",
|
||||
"filterEdit": "Edit filter",
|
||||
"filterDelete": "Delete filter",
|
||||
"filterDeleteTitle": "Delete the filter?",
|
||||
"filterName": "Name",
|
||||
"filterDescription": "Description",
|
||||
"filterQuery": "Query",
|
||||
"filterQueryHint": "today & p1",
|
||||
"filterFavorite": "Add to favourites",
|
||||
"filterUnfavorite": "Remove from favourites",
|
||||
"filterPreviewEmpty": "Nothing matches yet.",
|
||||
"filterNothing": "No tasks in this filter at the moment.",
|
||||
"filterHelp": "today, tomorrow, overdue, no date, 7 days, p1 to p4, #Project, ##Project with its sub-projects, /Section, @label, search: word. Combine them with & (and), | (or), ! (not) and parentheses; * stands for anything in a name.",
|
||||
"filterMenu": "Filter menu",
|
||||
"labelMenu": "Label menu",
|
||||
"labelNothing": "No open tasks have this label.",
|
||||
"labelEditEntry": "Edit label",
|
||||
"filterMatches": "{count, plural, =1{1 task matches} other{{count} tasks match}}",
|
||||
"@filterMatches": {
|
||||
"description": "The filter editor's preview.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"filterNotRead": "Not understood near “{near}”",
|
||||
"@filterNotRead": {
|
||||
"description": "The filter editor when its query cannot be read.",
|
||||
"placeholders": {
|
||||
"near": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"filterNotReadEnd": "This query ends too soon",
|
||||
"@filterNotReadEnd": {
|
||||
"description": "The filter editor when its query stops before it is complete."
|
||||
},
|
||||
"labelsTitle": "Labels",
|
||||
"labelsNone": "No labels yet.",
|
||||
"labelsNoneSet": "No labels",
|
||||
|
|
|
|||
|
|
@ -286,6 +286,150 @@ abstract class AppLocalizations {
|
|||
/// **'This device can\'t save: it doesn\'t hold your account\'s keys. Sign in with your password or recovery phrase to add them.'**
|
||||
String get keysMissing;
|
||||
|
||||
/// No description provided for @filtersLabelsTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Filters & labels'**
|
||||
String get filtersLabelsTitle;
|
||||
|
||||
/// No description provided for @filtersLabelsHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Views of your tasks, kept'**
|
||||
String get filtersLabelsHint;
|
||||
|
||||
/// No description provided for @filtersTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'My filters'**
|
||||
String get filtersTitle;
|
||||
|
||||
/// No description provided for @filtersNone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No filters yet.'**
|
||||
String get filtersNone;
|
||||
|
||||
/// No description provided for @filterAdd.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'New filter'**
|
||||
String get filterAdd;
|
||||
|
||||
/// No description provided for @filterEdit.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Edit filter'**
|
||||
String get filterEdit;
|
||||
|
||||
/// No description provided for @filterDelete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete filter'**
|
||||
String get filterDelete;
|
||||
|
||||
/// No description provided for @filterDeleteTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete the filter?'**
|
||||
String get filterDeleteTitle;
|
||||
|
||||
/// No description provided for @filterName.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Name'**
|
||||
String get filterName;
|
||||
|
||||
/// No description provided for @filterDescription.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Description'**
|
||||
String get filterDescription;
|
||||
|
||||
/// No description provided for @filterQuery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Query'**
|
||||
String get filterQuery;
|
||||
|
||||
/// No description provided for @filterQueryHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'today & p1'**
|
||||
String get filterQueryHint;
|
||||
|
||||
/// No description provided for @filterFavorite.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add to favourites'**
|
||||
String get filterFavorite;
|
||||
|
||||
/// No description provided for @filterUnfavorite.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Remove from favourites'**
|
||||
String get filterUnfavorite;
|
||||
|
||||
/// No description provided for @filterPreviewEmpty.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Nothing matches yet.'**
|
||||
String get filterPreviewEmpty;
|
||||
|
||||
/// No description provided for @filterNothing.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No tasks in this filter at the moment.'**
|
||||
String get filterNothing;
|
||||
|
||||
/// No description provided for @filterHelp.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'today, tomorrow, overdue, no date, 7 days, p1 to p4, #Project, ##Project with its sub-projects, /Section, @label, search: word. Combine them with & (and), | (or), ! (not) and parentheses; * stands for anything in a name.'**
|
||||
String get filterHelp;
|
||||
|
||||
/// No description provided for @filterMenu.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Filter menu'**
|
||||
String get filterMenu;
|
||||
|
||||
/// No description provided for @labelMenu.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Label menu'**
|
||||
String get labelMenu;
|
||||
|
||||
/// No description provided for @labelNothing.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No open tasks have this label.'**
|
||||
String get labelNothing;
|
||||
|
||||
/// No description provided for @labelEditEntry.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Edit label'**
|
||||
String get labelEditEntry;
|
||||
|
||||
/// The filter editor's preview.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{count, plural, =1{1 task matches} other{{count} tasks match}}'**
|
||||
String filterMatches(int count);
|
||||
|
||||
/// The filter editor when its query cannot be read.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Not understood near “{near}”'**
|
||||
String filterNotRead(String near);
|
||||
|
||||
/// The filter editor when its query stops before it is complete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'This query ends too soon'**
|
||||
String get filterNotReadEnd;
|
||||
|
||||
/// No description provided for @labelsTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -120,6 +120,89 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get keysMissing =>
|
||||
'Dieses Gerät kann nichts speichern: Es hat die Schlüssel deines Kontos nicht. Melde dich mit deinem Passwort oder deiner Wiederherstellungsphrase an, um sie hinzuzufügen.';
|
||||
|
||||
@override
|
||||
String get filtersLabelsTitle => 'Filter & Labels';
|
||||
|
||||
@override
|
||||
String get filtersLabelsHint => 'Gespeicherte Ansichten deiner Aufgaben';
|
||||
|
||||
@override
|
||||
String get filtersTitle => 'Meine Filter';
|
||||
|
||||
@override
|
||||
String get filtersNone => 'Noch keine Filter.';
|
||||
|
||||
@override
|
||||
String get filterAdd => 'Neuer Filter';
|
||||
|
||||
@override
|
||||
String get filterEdit => 'Filter bearbeiten';
|
||||
|
||||
@override
|
||||
String get filterDelete => 'Filter löschen';
|
||||
|
||||
@override
|
||||
String get filterDeleteTitle => 'Filter löschen?';
|
||||
|
||||
@override
|
||||
String get filterName => 'Name';
|
||||
|
||||
@override
|
||||
String get filterDescription => 'Beschreibung';
|
||||
|
||||
@override
|
||||
String get filterQuery => 'Abfrage';
|
||||
|
||||
@override
|
||||
String get filterQueryHint => 'heute & p1';
|
||||
|
||||
@override
|
||||
String get filterFavorite => 'Zu Favoriten hinzufügen';
|
||||
|
||||
@override
|
||||
String get filterUnfavorite => 'Aus Favoriten entfernen';
|
||||
|
||||
@override
|
||||
String get filterPreviewEmpty => 'Noch passt nichts.';
|
||||
|
||||
@override
|
||||
String get filterNothing => 'Gerade sind keine Aufgaben in diesem Filter.';
|
||||
|
||||
@override
|
||||
String get filterHelp =>
|
||||
'heute, morgen, überfällig, ohne datum, 7 tage, p1 bis p4, #Projekt, ##Projekt mit Unterprojekten, /Abschnitt, @label, suche: Wort. Verbinde sie mit & (und), | (oder), ! (nicht) und Klammern; * steht für Beliebiges in einem Namen.';
|
||||
|
||||
@override
|
||||
String get filterMenu => 'Filtermenü';
|
||||
|
||||
@override
|
||||
String get labelMenu => 'Labelmenü';
|
||||
|
||||
@override
|
||||
String get labelNothing => 'Keine offene Aufgabe hat dieses Label.';
|
||||
|
||||
@override
|
||||
String get labelEditEntry => 'Label bearbeiten';
|
||||
|
||||
@override
|
||||
String filterMatches(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count Aufgaben passen',
|
||||
one: '1 Aufgabe passt',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String filterNotRead(String near) {
|
||||
return 'Nicht verstanden bei „$near“';
|
||||
}
|
||||
|
||||
@override
|
||||
String get filterNotReadEnd => 'Die Abfrage endet zu früh';
|
||||
|
||||
@override
|
||||
String get labelsTitle => 'Labels';
|
||||
|
||||
|
|
|
|||
|
|
@ -120,6 +120,89 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get keysMissing =>
|
||||
'This device can\'t save: it doesn\'t hold your account\'s keys. Sign in with your password or recovery phrase to add them.';
|
||||
|
||||
@override
|
||||
String get filtersLabelsTitle => 'Filters & labels';
|
||||
|
||||
@override
|
||||
String get filtersLabelsHint => 'Views of your tasks, kept';
|
||||
|
||||
@override
|
||||
String get filtersTitle => 'My filters';
|
||||
|
||||
@override
|
||||
String get filtersNone => 'No filters yet.';
|
||||
|
||||
@override
|
||||
String get filterAdd => 'New filter';
|
||||
|
||||
@override
|
||||
String get filterEdit => 'Edit filter';
|
||||
|
||||
@override
|
||||
String get filterDelete => 'Delete filter';
|
||||
|
||||
@override
|
||||
String get filterDeleteTitle => 'Delete the filter?';
|
||||
|
||||
@override
|
||||
String get filterName => 'Name';
|
||||
|
||||
@override
|
||||
String get filterDescription => 'Description';
|
||||
|
||||
@override
|
||||
String get filterQuery => 'Query';
|
||||
|
||||
@override
|
||||
String get filterQueryHint => 'today & p1';
|
||||
|
||||
@override
|
||||
String get filterFavorite => 'Add to favourites';
|
||||
|
||||
@override
|
||||
String get filterUnfavorite => 'Remove from favourites';
|
||||
|
||||
@override
|
||||
String get filterPreviewEmpty => 'Nothing matches yet.';
|
||||
|
||||
@override
|
||||
String get filterNothing => 'No tasks in this filter at the moment.';
|
||||
|
||||
@override
|
||||
String get filterHelp =>
|
||||
'today, tomorrow, overdue, no date, 7 days, p1 to p4, #Project, ##Project with its sub-projects, /Section, @label, search: word. Combine them with & (and), | (or), ! (not) and parentheses; * stands for anything in a name.';
|
||||
|
||||
@override
|
||||
String get filterMenu => 'Filter menu';
|
||||
|
||||
@override
|
||||
String get labelMenu => 'Label menu';
|
||||
|
||||
@override
|
||||
String get labelNothing => 'No open tasks have this label.';
|
||||
|
||||
@override
|
||||
String get labelEditEntry => 'Edit label';
|
||||
|
||||
@override
|
||||
String filterMatches(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count tasks match',
|
||||
one: '1 task matches',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String filterNotRead(String near) {
|
||||
return 'Not understood near “$near”';
|
||||
}
|
||||
|
||||
@override
|
||||
String get filterNotReadEnd => 'This query ends too soon';
|
||||
|
||||
@override
|
||||
String get labelsTitle => 'Labels';
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import 'package:halkyon_todo_client/halkyon_todo_client.dart'
|
|||
import 'package:halkyon_todo_flutter/l10n/app_localizations.dart';
|
||||
import 'package:halkyon_todo_flutter/src/core/privacy/sync_content_key_gateway.dart';
|
||||
import 'package:halkyon_todo_flutter/src/core/privacy/todo_keys.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/filter/data/filter_repository.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/filter/data/sync_filter_gateway.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/label/data/label_repository.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/label/data/sync_label_gateway.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/location/data/location_access.dart';
|
||||
|
|
@ -62,6 +64,9 @@ class TodoPerson(
|
|||
/// Their labels, sealed and synced.
|
||||
required final LabelRepository labels,
|
||||
|
||||
/// Their filters, sealed and synced (D35).
|
||||
required final FilterRepository filters,
|
||||
|
||||
/// Their settings of Todo's (Upcoming's Display), sealed and synced.
|
||||
required final SettingsRepository settings,
|
||||
|
||||
|
|
@ -406,6 +411,7 @@ Future<TodoPerson> _openPersonData(
|
|||
automaticReminder: () async => (await settings.read()).reminders.automatic,
|
||||
);
|
||||
final labels = LabelRepository(SyncLabelGateway(db, sync), keys);
|
||||
final filters = FilterRepository(SyncFilterGateway(db, sync), keys);
|
||||
// The Inbox, made once by whichever device is first; nothing where this
|
||||
// device holds none of the account's keys.
|
||||
unawaited(
|
||||
|
|
@ -422,6 +428,7 @@ Future<TodoPerson> _openPersonData(
|
|||
sections: sections,
|
||||
tasks: tasks,
|
||||
labels: labels,
|
||||
filters: filters,
|
||||
settings: settings,
|
||||
reminders: reminders,
|
||||
notifications: notifications,
|
||||
|
|
@ -434,6 +441,7 @@ Future<TodoPerson> _openPersonData(
|
|||
await sections.dispose();
|
||||
await tasks.dispose();
|
||||
await labels.dispose();
|
||||
await filters.dispose();
|
||||
await settings.dispose();
|
||||
await sync.dispose();
|
||||
await db.close();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,15 @@ abstract final class TodoPaths() {
|
|||
/// A project's screen, in Browse.
|
||||
static String project(String id) => '/browse/projects/$id';
|
||||
|
||||
/// Filters and labels (D35), in Browse.
|
||||
static const filters = '/browse/filters';
|
||||
|
||||
/// A filter's tasks.
|
||||
static String filter(String id) => '/browse/filters/$id';
|
||||
|
||||
/// A label's tasks.
|
||||
static String label(String id) => '/browse/labels/$id';
|
||||
|
||||
/// Settings, over the tabs.
|
||||
static const settings = '/settings';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import 'package:go_router/go_router.dart';
|
||||
import 'package:halkyon_app/halkyon_app.dart';
|
||||
import 'package:halkyon_todo_flutter/src/app/todo_paths.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/filter/widget/filter_screen.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/filter/widget/filters_screen.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/project/widget/project_screen.dart';
|
||||
import 'package:halkyon_todo_flutter/src/settings/settings_screen.dart';
|
||||
import 'package:halkyon_todo_flutter/src/tabs/browse_tab.dart';
|
||||
|
|
@ -58,6 +60,22 @@ GoRouter buildTodoRouter() => GoRouter(
|
|||
builder: (context, state) =>
|
||||
ProjectScreen(projectId: state.pathParameters['id']!),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'filters',
|
||||
builder: (context, state) => const FiltersScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':id',
|
||||
builder: (context, state) =>
|
||||
FilterScreen(filterId: state.pathParameters['id']!),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: 'labels/:id',
|
||||
builder: (context, state) =>
|
||||
LabelScreen(labelId: state.pathParameters['id']!),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:halkyon_todo_flutter/src/core/sealed/json_payload.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/filter/model/saved_filter.dart';
|
||||
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||
|
||||
/// The schema of a filter's payload this app writes and reads.
|
||||
const filterSchemaVersion = 1;
|
||||
|
||||
/// [content] as its row's payload, before sealing.
|
||||
ByteData encodeFilter(FilterContent content) =>
|
||||
encodeJson(filterSchemaVersion, {
|
||||
'name': content.name,
|
||||
'query': content.query,
|
||||
if (content.description.isNotEmpty) 'description': content.description,
|
||||
'color': ?content.color?.name,
|
||||
if (content.favorite) 'favorite': true,
|
||||
'position': content.position,
|
||||
});
|
||||
|
||||
/// The filter [payload] holds, opened; null for one it can't read.
|
||||
FilterContent? decodeFilter(ByteData payload) => decodeJson(
|
||||
payload,
|
||||
version: filterSchemaVersion,
|
||||
name: 'Filter',
|
||||
read: (json) => FilterContent(
|
||||
name: json['name']! as String,
|
||||
query: json['query']! as String,
|
||||
description: json['description'] as String? ?? '',
|
||||
color: PaletteColor.byName(json['color'] as String?),
|
||||
favorite: json['favorite'] as bool? ?? false,
|
||||
position: (json['position'] as num?)?.toDouble() ?? 0,
|
||||
),
|
||||
);
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import 'package:halkyon_todo_flutter/src/core/privacy/todo_keys.dart';
|
||||
import 'package:halkyon_todo_flutter/src/core/sealed/sealed_row_gateway.dart';
|
||||
import 'package:halkyon_todo_flutter/src/core/sealed/sealed_rows.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/filter/data/filter_payload.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/filter/model/saved_filter.dart';
|
||||
|
||||
/// The person's filters (ADR 0033, D35), each sealed with their key for its
|
||||
/// row (D5, [SealedRows]).
|
||||
class FilterRepository(final SealedRowGateway _gateway, final TodoKeys _keys) {
|
||||
late final _rows = SealedRows<FilterContent>(
|
||||
_gateway,
|
||||
_keys,
|
||||
table: 'saved_filter',
|
||||
encode: encodeFilter,
|
||||
decode: decodeFilter,
|
||||
);
|
||||
|
||||
/// Fires when the filters changed, here or on another device.
|
||||
Stream<void> get changes => _rows.changes;
|
||||
|
||||
/// Every filter, by when it was made.
|
||||
Future<List<StoredFilter>> all() async => [
|
||||
for (final (:id, :content) in await _rows.all()) StoredFilter(id, content),
|
||||
];
|
||||
|
||||
/// Keeps a new filter and returns its ID; throws [KeysMissing] where this
|
||||
/// device can't seal.
|
||||
Future<String> add(FilterContent content) => _rows.insert(content);
|
||||
|
||||
Future<void> update(String id, FilterContent content) =>
|
||||
_rows.update(id, content);
|
||||
|
||||
Future<void> delete(String id) => _rows.delete([id]);
|
||||
|
||||
/// Closes what it holds open.
|
||||
Future<void> dispose() => _rows.dispose();
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:halkyon_app/halkyon_app.dart';
|
||||
import 'package:halkyon_todo_client/halkyon_todo_client.dart' as protocol;
|
||||
import 'package:halkyon_todo_flutter/src/core/sealed/sealed_row_gateway.dart';
|
||||
import 'package:serverpod_offline_sync_client/offline_sync.dart';
|
||||
|
||||
/// The person's filters on their local database ([SealedRowGateway]), which the
|
||||
/// sync engine keeps in step with their other devices.
|
||||
class SyncFilterGateway(
|
||||
final OfflineSyncDatabaseSession _db,
|
||||
final SyncService _sync,
|
||||
) implements SealedRowGateway {
|
||||
@override
|
||||
Future<List<SealedRow>> rows() async => [
|
||||
for (final row in await protocol.SavedFilter.db.find(
|
||||
_db,
|
||||
orderBy: (t) => t.id,
|
||||
))
|
||||
(
|
||||
id: row.id!.uuid,
|
||||
envelope: (keyId: row.keyId, nonce: row.nonce, payload: row.payload),
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<void> insert(SealedRow row) async {
|
||||
await protocol.SavedFilter.db.insertRow(
|
||||
_db,
|
||||
protocol.SavedFilter(
|
||||
id: protocol.UuidValue.withValidation(row.id),
|
||||
keyId: row.envelope.keyId,
|
||||
nonce: row.envelope.nonce,
|
||||
payload: row.envelope.payload,
|
||||
),
|
||||
);
|
||||
_sync.requestSync();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> update(String id, Envelope envelope) async {
|
||||
if (await protocol.SavedFilter.db.findById(
|
||||
_db,
|
||||
protocol.UuidValue.withValidation(id),
|
||||
)
|
||||
case final row?) {
|
||||
// Only the named columns change (ADR 0001, rule 1).
|
||||
await protocol.SavedFilter.db.updateRow(
|
||||
_db,
|
||||
row.copyWith(
|
||||
keyId: envelope.keyId,
|
||||
nonce: envelope.nonce,
|
||||
payload: envelope.payload,
|
||||
),
|
||||
columns: (t) => [t.keyId, t.nonce, t.payload],
|
||||
);
|
||||
_sync.requestSync();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(Iterable<String> ids) async {
|
||||
final uuids = <protocol.UuidValue>{
|
||||
for (final id in ids) protocol.UuidValue.withValidation(id),
|
||||
};
|
||||
if (uuids.isEmpty) {
|
||||
return;
|
||||
}
|
||||
await _db.db.transaction((transaction) async {
|
||||
// Blanked first (ADR 0002, rule 2): the instance keeps deleted rows as
|
||||
// they last were, until the space goes.
|
||||
for (final row in await protocol.SavedFilter.db.find(
|
||||
_db,
|
||||
where: (t) => t.id.inSet(uuids),
|
||||
transaction: transaction,
|
||||
)) {
|
||||
await protocol.SavedFilter.db.updateRow(
|
||||
_db,
|
||||
protocol.SavedFilter(
|
||||
id: row.id,
|
||||
spaceId: row.spaceId,
|
||||
payload: ByteData(0),
|
||||
),
|
||||
columns: (t) => [t.keyId, t.nonce, t.payload],
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
await protocol.SavedFilter.db.deleteWhere(
|
||||
_db,
|
||||
where: (t) => t.id.inSet(uuids),
|
||||
transaction: transaction,
|
||||
);
|
||||
});
|
||||
_sync.requestSync();
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<void> get remoteChanges => _sync.merges;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue