feat(todo): a project's Calendar, Upcoming's Display and its 3-day grid (ADR 0033, D17)
A project's Display gains the Calendar layout: Upcoming's week strip and agenda, now shared as DayAgenda, over the project's tasks it keeps, with No date beside the month and Future occurrences, each repeating task also on the days it will come back (calendarOf on Repeat.next), muted, neither ticked off nor dragged. Upcoming gets a Display of its own, the same sheet over another target (DisplayTarget): Agenda or 3-day, done tasks, future occurrences, sorting, the filters by deadline, priority and label, and in the grid Colouring by priority, label or project. It is kept in the person's settings, one sealed row under an ID keyed by the account in the new synced table todo_settings (migration settings: the table, its index on spaceId and its foreign key to the space, cascading on delete). The 3-day grid shows today and the two days after, stepped or swiped three at a time: tasks without a time all day, timed ones as blocks as long as they last, overlapping ones side by side (layOutDay). A long press on a half hour adds a task there for half an hour; Overdue opens the overdue tasks with Reschedule. The board's separator builder names its BuildContext, as the root's analyzer plugin asks (never_discard_build_context). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
e4fbfb54d1
commit
2939437c74
57 changed files with 21845 additions and 314 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,530 @@
|
|||
{
|
||||
"__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": "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": "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
|
||||
}
|
||||
}
|
||||
],
|
||||
"warnings": [],
|
||||
"migrationApiVersion": 1
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import 'package:serverpod_database/serverpod_database.dart';
|
|||
|
||||
part '20261101000019144-projects/migration.dart';
|
||||
part '20261101000604776-tasks/migration.dart';
|
||||
part '20261101180755366-settings/migration.dart';
|
||||
|
||||
/// Migration registry for the client-side database.
|
||||
class MigrationRegistry {
|
||||
|
|
@ -14,6 +15,7 @@ class MigrationRegistry {
|
|||
static final List<MigrationVersionSql> migrations = [
|
||||
_Migration20261101000019144_projects(),
|
||||
_Migration20261101000604776_tasks(),
|
||||
_Migration20261101180755366_settings(),
|
||||
];
|
||||
|
||||
/// List of all client-side database migration versions.
|
||||
|
|
|
|||
|
|
@ -25,11 +25,13 @@ import 'label/label.dart' as _ivak08z4;
|
|||
import 'privacy/content_key.dart' as _imed3x5d;
|
||||
import 'project/project.dart' as _i9c44spp;
|
||||
import 'project/section.dart' as _imadkvi9;
|
||||
import 'settings/todo_settings.dart' as _icak60vd;
|
||||
import 'task/task.dart' as _ib4f8uyw;
|
||||
export 'label/label.dart';
|
||||
export 'privacy/content_key.dart';
|
||||
export 'project/project.dart';
|
||||
export 'project/section.dart';
|
||||
export 'settings/todo_settings.dart';
|
||||
export 'task/task.dart';
|
||||
export 'client.dart';
|
||||
export 'sync_tables.dart';
|
||||
|
|
@ -371,6 +373,73 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
],
|
||||
managed: true,
|
||||
),
|
||||
_isd.TableDefinition(
|
||||
name: 'todo_settings',
|
||||
dartName: 'TodoSettings',
|
||||
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: 'todo_settings_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: 'todo_settings_space_idx',
|
||||
tableSpace: null,
|
||||
elements: [
|
||||
_isd.IndexElementDefinition(
|
||||
type: _isd.IndexElementDefinitionType.column,
|
||||
definition: 'spaceId',
|
||||
),
|
||||
],
|
||||
type: 'btree',
|
||||
isUnique: false,
|
||||
isPrimary: false,
|
||||
),
|
||||
],
|
||||
managed: true,
|
||||
),
|
||||
..._ihk4p5t7.Protocol() is _isd.DatabaseSerializationManager
|
||||
? (_ihk4p5t7.Protocol() as _isd.DatabaseSerializationManager)
|
||||
.getTargetTableDefinitions()
|
||||
|
|
@ -432,6 +501,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _imadkvi9.Section) {
|
||||
return _imadkvi9.Section.fromJson(data) as T;
|
||||
}
|
||||
if (t == _icak60vd.TodoSettings) {
|
||||
return _icak60vd.TodoSettings.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ib4f8uyw.Task) {
|
||||
return _ib4f8uyw.Task.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -447,6 +519,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _isc.getType<_imadkvi9.Section?>()) {
|
||||
return (data != null ? _imadkvi9.Section.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_icak60vd.TodoSettings?>()) {
|
||||
return (data != null ? _icak60vd.TodoSettings.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_ib4f8uyw.Task?>()) {
|
||||
return (data != null ? _ib4f8uyw.Task.fromJson(data) : null) as T;
|
||||
}
|
||||
|
|
@ -474,6 +549,7 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
_imed3x5d.ContentKey => 'ContentKey',
|
||||
_i9c44spp.Project => 'Project',
|
||||
_imadkvi9.Section => 'Section',
|
||||
_icak60vd.TodoSettings => 'TodoSettings',
|
||||
_ib4f8uyw.Task => 'Task',
|
||||
_ => null,
|
||||
};
|
||||
|
|
@ -500,6 +576,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return 'Project';
|
||||
case _imadkvi9.Section():
|
||||
return 'Section';
|
||||
case _icak60vd.TodoSettings():
|
||||
return 'TodoSettings';
|
||||
case _ib4f8uyw.Task():
|
||||
return 'Task';
|
||||
}
|
||||
|
|
@ -550,6 +628,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (dataClassName == 'Section') {
|
||||
return deserialize<_imadkvi9.Section>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'TodoSettings') {
|
||||
return deserialize<_icak60vd.TodoSettings>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'Task') {
|
||||
return deserialize<_ib4f8uyw.Task>(data['data']);
|
||||
}
|
||||
|
|
@ -640,6 +721,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return _i9c44spp.Project.t;
|
||||
case _imadkvi9.Section:
|
||||
return _imadkvi9.Section.t;
|
||||
case _icak60vd.TodoSettings:
|
||||
return _icak60vd.TodoSettings.t;
|
||||
case _ib4f8uyw.Task:
|
||||
return _ib4f8uyw.Task.t;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// The person's settings of Todo's (ADR 0033, D17): one row of theirs, under
|
||||
/// an ID keyed by their account, holding Upcoming's Display (and D4's
|
||||
/// settings to come) in its sealed payload (D5); the instance sees that it
|
||||
/// exists, its size and when it changed. The last writer wins. Synced.
|
||||
abstract class TodoSettings
|
||||
implements _isd.TableRow<_isc.UuidValue?>, _isc.ProtocolSerialization {
|
||||
TodoSettings._({
|
||||
this.id,
|
||||
this.spaceId,
|
||||
this.keyId,
|
||||
required this.payload,
|
||||
this.nonce,
|
||||
});
|
||||
|
||||
factory TodoSettings({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
required _idt.ByteData payload,
|
||||
_idt.ByteData? nonce,
|
||||
}) = _TodoSettingsImpl;
|
||||
|
||||
factory TodoSettings.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return TodoSettings(
|
||||
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 = TodoSettingsTable();
|
||||
|
||||
static const db = TodoSettingsRepository._();
|
||||
|
||||
@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 [TodoSettings]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
TodoSettings copyWith({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? keyId,
|
||||
_idt.ByteData? payload,
|
||||
_idt.ByteData? nonce,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'TodoSettings',
|
||||
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__': 'TodoSettings',
|
||||
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 TodoSettingsInclude include() {
|
||||
return TodoSettingsInclude._();
|
||||
}
|
||||
|
||||
static TodoSettingsIncludeList includeList({
|
||||
_isd.WhereExpressionBuilder<TodoSettingsTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<TodoSettingsTable>? orderBy,
|
||||
_isd.OrderByListBuilder<TodoSettingsTable>? orderByList,
|
||||
TodoSettingsInclude? include,
|
||||
}) {
|
||||
return TodoSettingsIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(TodoSettings.t),
|
||||
orderByList: orderByList?.call(TodoSettings.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _TodoSettingsImpl extends TodoSettings {
|
||||
_TodoSettingsImpl({
|
||||
_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 [TodoSettings]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
TodoSettings copyWith({
|
||||
Object? id = _Undefined,
|
||||
Object? spaceId = _Undefined,
|
||||
Object? keyId = _Undefined,
|
||||
_idt.ByteData? payload,
|
||||
Object? nonce = _Undefined,
|
||||
}) {
|
||||
return TodoSettings(
|
||||
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 TodoSettingsUpdateTable extends _isd.UpdateTable<TodoSettingsTable> {
|
||||
TodoSettingsUpdateTable(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 TodoSettingsTable extends _isd.Table<_isc.UuidValue?> {
|
||||
TodoSettingsTable({super.tableRelation}) : super(tableName: 'todo_settings') {
|
||||
updateTable = TodoSettingsUpdateTable(this);
|
||||
spaceId = _isd.ColumnInt(
|
||||
'spaceId',
|
||||
this,
|
||||
);
|
||||
keyId = _isd.ColumnString(
|
||||
'keyId',
|
||||
this,
|
||||
);
|
||||
payload = _isd.ColumnByteData(
|
||||
'payload',
|
||||
this,
|
||||
);
|
||||
nonce = _isd.ColumnByteData(
|
||||
'nonce',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final TodoSettingsUpdateTable 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 TodoSettingsInclude extends _isd.IncludeObject {
|
||||
TodoSettingsInclude._();
|
||||
|
||||
@override
|
||||
Map<String, _isd.Include?> get includes => {};
|
||||
|
||||
@override
|
||||
_isd.Table<_isc.UuidValue?> get table => TodoSettings.t;
|
||||
}
|
||||
|
||||
class TodoSettingsIncludeList extends _isd.IncludeList {
|
||||
TodoSettingsIncludeList._({
|
||||
_isd.WhereExpressionBuilder<TodoSettingsTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(TodoSettings.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _isd.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_isd.Table<_isc.UuidValue?> get table => TodoSettings.t;
|
||||
}
|
||||
|
||||
class TodoSettingsRepository {
|
||||
const TodoSettingsRepository._();
|
||||
|
||||
/// Returns a list of [TodoSettings]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<TodoSettings>> find(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<TodoSettingsTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<TodoSettingsTable>? orderBy,
|
||||
_isd.OrderByListBuilder<TodoSettingsTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<TodoSettings>(
|
||||
where: where?.call(TodoSettings.t),
|
||||
orderBy: orderBy?.call(TodoSettings.t),
|
||||
orderByList: orderByList?.call(TodoSettings.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [TodoSettings] 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<TodoSettings?> findFirstRow(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<TodoSettingsTable>? where,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<TodoSettingsTable>? orderBy,
|
||||
_isd.OrderByListBuilder<TodoSettingsTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<TodoSettings>(
|
||||
where: where?.call(TodoSettings.t),
|
||||
orderBy: orderBy?.call(TodoSettings.t),
|
||||
orderByList: orderByList?.call(TodoSettings.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [TodoSettings] by its [id] or null if no such row exists.
|
||||
Future<TodoSettings?> findById(
|
||||
_isd.DatabaseSession session,
|
||||
_isc.UuidValue id, {
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<TodoSettings>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [TodoSettings]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [TodoSettings]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<TodoSettings>> insert(
|
||||
_isd.DatabaseSession session,
|
||||
List<TodoSettings> rows, {
|
||||
_isd.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<TodoSettings>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [TodoSettings] and returns the inserted row.
|
||||
///
|
||||
/// The returned [TodoSettings] will have its `id` field set.
|
||||
Future<TodoSettings> insertRow(
|
||||
_isd.DatabaseSession session,
|
||||
TodoSettings row, {
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<TodoSettings>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [TodoSettings]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 [TodoSettings]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<TodoSettings>> upsert(
|
||||
_isd.DatabaseSession session,
|
||||
List<TodoSettings> rows, {
|
||||
required _isd.ColumnSelections<TodoSettingsTable> conflictColumns,
|
||||
_isd.ColumnSelections<TodoSettingsTable>? updateColumns,
|
||||
_isd.WhereExpressionBuilder<TodoSettingsTable>? updateWhere,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<TodoSettings>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(TodoSettings.t),
|
||||
updateColumns: updateColumns?.call(TodoSettings.t),
|
||||
updateWhere: updateWhere?.call(TodoSettings.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [TodoSettings] 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 [TodoSettings] will have its `id` field set.
|
||||
Future<TodoSettings?> upsertRow(
|
||||
_isd.DatabaseSession session,
|
||||
TodoSettings row, {
|
||||
required _isd.ColumnSelections<TodoSettingsTable> conflictColumns,
|
||||
_isd.ColumnSelections<TodoSettingsTable>? updateColumns,
|
||||
_isd.WhereExpressionBuilder<TodoSettingsTable>? updateWhere,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<TodoSettings>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(TodoSettings.t),
|
||||
updateColumns: updateColumns?.call(TodoSettings.t),
|
||||
updateWhere: updateWhere?.call(TodoSettings.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [TodoSettings]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<TodoSettings>> update(
|
||||
_isd.DatabaseSession session,
|
||||
List<TodoSettings> rows, {
|
||||
_isd.ColumnSelections<TodoSettingsTable>? columns,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<TodoSettings>(
|
||||
rows,
|
||||
columns: columns?.call(TodoSettings.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [TodoSettings]. 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<TodoSettings> updateRow(
|
||||
_isd.DatabaseSession session,
|
||||
TodoSettings row, {
|
||||
_isd.ColumnSelections<TodoSettingsTable>? columns,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<TodoSettings>(
|
||||
row,
|
||||
columns: columns?.call(TodoSettings.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [TodoSettings] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<TodoSettings?> updateById(
|
||||
_isd.DatabaseSession session,
|
||||
_isc.UuidValue id, {
|
||||
required _isd.ColumnValueListBuilder<TodoSettingsUpdateTable> columnValues,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<TodoSettings>(
|
||||
id,
|
||||
columnValues: columnValues(TodoSettings.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [TodoSettings]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<TodoSettings>> updateWhere(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.ColumnValueListBuilder<TodoSettingsUpdateTable> columnValues,
|
||||
required _isd.WhereExpressionBuilder<TodoSettingsTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<TodoSettingsTable>? orderBy,
|
||||
_isd.OrderByListBuilder<TodoSettingsTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<TodoSettings>(
|
||||
columnValues: columnValues(TodoSettings.t.updateTable),
|
||||
where: where(TodoSettings.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(TodoSettings.t),
|
||||
orderByList: orderByList?.call(TodoSettings.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [TodoSettings]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<TodoSettings>> delete(
|
||||
_isd.DatabaseSession session,
|
||||
List<TodoSettings> rows, {
|
||||
_isd.OrderByBuilder<TodoSettingsTable>? orderBy,
|
||||
_isd.OrderByListBuilder<TodoSettingsTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<TodoSettings>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(TodoSettings.t),
|
||||
orderByList: orderByList?.call(TodoSettings.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [TodoSettings].
|
||||
Future<TodoSettings> deleteRow(
|
||||
_isd.DatabaseSession session,
|
||||
TodoSettings row, {
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<TodoSettings>(
|
||||
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<TodoSettings>> deleteWhere(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.WhereExpressionBuilder<TodoSettingsTable> where,
|
||||
_isd.OrderByBuilder<TodoSettingsTable>? orderBy,
|
||||
_isd.OrderByListBuilder<TodoSettingsTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<TodoSettings>(
|
||||
where: where(TodoSettings.t),
|
||||
orderBy: orderBy?.call(TodoSettings.t),
|
||||
orderByList: orderByList?.call(TodoSettings.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<TodoSettingsTable>? where,
|
||||
int? limit,
|
||||
_isd.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<TodoSettings>(
|
||||
where: where?.call(TodoSettings.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [TodoSettings] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.WhereExpressionBuilder<TodoSettingsTable> where,
|
||||
required _isd.LockMode lockMode,
|
||||
required _isd.Transaction transaction,
|
||||
_isd.LockBehavior lockBehavior = _isd.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<TodoSettings>(
|
||||
where: where(TodoSettings.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import 'label/label.dart' as _ivak08z4;
|
|||
import 'privacy/content_key.dart' as _imed3x5d;
|
||||
import 'project/project.dart' as _i9c44spp;
|
||||
import 'project/section.dart' as _imadkvi9;
|
||||
import 'settings/todo_settings.dart' as _icak60vd;
|
||||
import 'task/task.dart' as _ib4f8uyw;
|
||||
|
||||
/// The tables synchronized between client and server by the
|
||||
|
|
@ -26,4 +27,5 @@ final List<_isd.Table> syncTables = [
|
|||
_i9c44spp.Project.t,
|
||||
_imadkvi9.Section.t,
|
||||
_ib4f8uyw.Task.t,
|
||||
_icak60vd.TodoSettings.t,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -206,5 +206,19 @@
|
|||
"layoutTitle": "Layout",
|
||||
"layoutList": "Liste",
|
||||
"layoutBoard": "Board",
|
||||
"boardNoSection": "(Kein Abschnitt)"
|
||||
"boardNoSection": "(Kein Abschnitt)",
|
||||
"layoutCalendar": "Kalender",
|
||||
"displayOccurrences": "Künftige Wiederholungen",
|
||||
"calendarNoDate": "Ohne Datum",
|
||||
"calendarNoDateEmpty": "Jede Aufgabe hier hat ein Datum",
|
||||
"calendarNoDateHint": "Aufgaben dieses Projekts ohne Datum stehen hier.",
|
||||
"taskLater": "Eine spätere Wiederholung",
|
||||
"sortingSmart": "Smart",
|
||||
"layoutAgenda": "Agenda",
|
||||
"layoutThreeDay": "3 Tage",
|
||||
"displayColoring": "Farben",
|
||||
"coloringProject": "Projekt",
|
||||
"allDay": "Ganztägig",
|
||||
"daysPrevious": "Frühere Tage",
|
||||
"daysNext": "Spätere Tage"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -624,5 +624,61 @@
|
|||
"boardNoSection": "(No section)",
|
||||
"@boardNoSection": {
|
||||
"description": "A project's layout in its Display, and its board's columns (ADR 0033, D17)."
|
||||
},
|
||||
"layoutCalendar": "Calendar",
|
||||
"@layoutCalendar": {
|
||||
"description": "A project's Calendar layout and its Display (ADR 0033, D17)."
|
||||
},
|
||||
"displayOccurrences": "Future occurrences",
|
||||
"@displayOccurrences": {
|
||||
"description": "A project's Calendar layout and its Display (ADR 0033, D17)."
|
||||
},
|
||||
"calendarNoDate": "No date",
|
||||
"@calendarNoDate": {
|
||||
"description": "A project's Calendar layout and its Display (ADR 0033, D17)."
|
||||
},
|
||||
"calendarNoDateEmpty": "Every task here has a date",
|
||||
"@calendarNoDateEmpty": {
|
||||
"description": "A project's Calendar layout and its Display (ADR 0033, D17)."
|
||||
},
|
||||
"calendarNoDateHint": "Tasks of this project without a date show here.",
|
||||
"@calendarNoDateHint": {
|
||||
"description": "A project's Calendar layout and its Display (ADR 0033, D17)."
|
||||
},
|
||||
"taskLater": "A later time of a repeating task",
|
||||
"@taskLater": {
|
||||
"description": "A project's Calendar layout and its Display (ADR 0033, D17)."
|
||||
},
|
||||
"sortingSmart": "Smart",
|
||||
"@sortingSmart": {
|
||||
"description": "Upcoming's Display and its 3-day grid (ADR 0033, D17)."
|
||||
},
|
||||
"layoutAgenda": "Agenda",
|
||||
"@layoutAgenda": {
|
||||
"description": "Upcoming's Display and its 3-day grid (ADR 0033, D17)."
|
||||
},
|
||||
"layoutThreeDay": "3-day",
|
||||
"@layoutThreeDay": {
|
||||
"description": "Upcoming's Display and its 3-day grid (ADR 0033, D17)."
|
||||
},
|
||||
"displayColoring": "Colouring",
|
||||
"@displayColoring": {
|
||||
"description": "Upcoming's Display and its 3-day grid (ADR 0033, D17)."
|
||||
},
|
||||
"coloringProject": "Project",
|
||||
"@coloringProject": {
|
||||
"description": "Upcoming's Display and its 3-day grid (ADR 0033, D17)."
|
||||
},
|
||||
"allDay": "All day",
|
||||
"@allDay": {
|
||||
"description": "Upcoming's Display and its 3-day grid (ADR 0033, D17)."
|
||||
},
|
||||
"daysPrevious": "Earlier days",
|
||||
"@daysPrevious": {
|
||||
"description": "Upcoming's Display and its 3-day grid (ADR 0033, D17)."
|
||||
},
|
||||
"daysNext": "Later days",
|
||||
"@daysNext": {
|
||||
"description": "Upcoming's Display and its 3-day grid (ADR 0033, D17)."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1341,6 +1341,90 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'(No section)'**
|
||||
String get boardNoSection;
|
||||
|
||||
/// A project's Calendar layout and its Display (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Calendar'**
|
||||
String get layoutCalendar;
|
||||
|
||||
/// A project's Calendar layout and its Display (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Future occurrences'**
|
||||
String get displayOccurrences;
|
||||
|
||||
/// A project's Calendar layout and its Display (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No date'**
|
||||
String get calendarNoDate;
|
||||
|
||||
/// A project's Calendar layout and its Display (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Every task here has a date'**
|
||||
String get calendarNoDateEmpty;
|
||||
|
||||
/// A project's Calendar layout and its Display (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tasks of this project without a date show here.'**
|
||||
String get calendarNoDateHint;
|
||||
|
||||
/// A project's Calendar layout and its Display (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'A later time of a repeating task'**
|
||||
String get taskLater;
|
||||
|
||||
/// Upcoming's Display and its 3-day grid (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Smart'**
|
||||
String get sortingSmart;
|
||||
|
||||
/// Upcoming's Display and its 3-day grid (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Agenda'**
|
||||
String get layoutAgenda;
|
||||
|
||||
/// Upcoming's Display and its 3-day grid (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'3-day'**
|
||||
String get layoutThreeDay;
|
||||
|
||||
/// Upcoming's Display and its 3-day grid (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Colouring'**
|
||||
String get displayColoring;
|
||||
|
||||
/// Upcoming's Display and its 3-day grid (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Project'**
|
||||
String get coloringProject;
|
||||
|
||||
/// Upcoming's Display and its 3-day grid (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'All day'**
|
||||
String get allDay;
|
||||
|
||||
/// Upcoming's Display and its 3-day grid (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Earlier days'**
|
||||
String get daysPrevious;
|
||||
|
||||
/// Upcoming's Display and its 3-day grid (ADR 0033, D17).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Later days'**
|
||||
String get daysNext;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -833,4 +833,47 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get boardNoSection => '(Kein Abschnitt)';
|
||||
|
||||
@override
|
||||
String get layoutCalendar => 'Kalender';
|
||||
|
||||
@override
|
||||
String get displayOccurrences => 'Künftige Wiederholungen';
|
||||
|
||||
@override
|
||||
String get calendarNoDate => 'Ohne Datum';
|
||||
|
||||
@override
|
||||
String get calendarNoDateEmpty => 'Jede Aufgabe hier hat ein Datum';
|
||||
|
||||
@override
|
||||
String get calendarNoDateHint =>
|
||||
'Aufgaben dieses Projekts ohne Datum stehen hier.';
|
||||
|
||||
@override
|
||||
String get taskLater => 'Eine spätere Wiederholung';
|
||||
|
||||
@override
|
||||
String get sortingSmart => 'Smart';
|
||||
|
||||
@override
|
||||
String get layoutAgenda => 'Agenda';
|
||||
|
||||
@override
|
||||
String get layoutThreeDay => '3 Tage';
|
||||
|
||||
@override
|
||||
String get displayColoring => 'Farben';
|
||||
|
||||
@override
|
||||
String get coloringProject => 'Projekt';
|
||||
|
||||
@override
|
||||
String get allDay => 'Ganztägig';
|
||||
|
||||
@override
|
||||
String get daysPrevious => 'Frühere Tage';
|
||||
|
||||
@override
|
||||
String get daysNext => 'Spätere Tage';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -832,6 +832,49 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get boardNoSection => '(No section)';
|
||||
|
||||
@override
|
||||
String get layoutCalendar => 'Calendar';
|
||||
|
||||
@override
|
||||
String get displayOccurrences => 'Future occurrences';
|
||||
|
||||
@override
|
||||
String get calendarNoDate => 'No date';
|
||||
|
||||
@override
|
||||
String get calendarNoDateEmpty => 'Every task here has a date';
|
||||
|
||||
@override
|
||||
String get calendarNoDateHint =>
|
||||
'Tasks of this project without a date show here.';
|
||||
|
||||
@override
|
||||
String get taskLater => 'A later time of a repeating task';
|
||||
|
||||
@override
|
||||
String get sortingSmart => 'Smart';
|
||||
|
||||
@override
|
||||
String get layoutAgenda => 'Agenda';
|
||||
|
||||
@override
|
||||
String get layoutThreeDay => '3-day';
|
||||
|
||||
@override
|
||||
String get displayColoring => 'Colouring';
|
||||
|
||||
@override
|
||||
String get coloringProject => 'Project';
|
||||
|
||||
@override
|
||||
String get allDay => 'All day';
|
||||
|
||||
@override
|
||||
String get daysPrevious => 'Earlier days';
|
||||
|
||||
@override
|
||||
String get daysNext => 'Later days';
|
||||
}
|
||||
|
||||
/// The translations for English, as used in the United Kingdom (`en_GB`).
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import 'package:halkyon_todo_flutter/src/feature/reminder/data/reminder_gateway.
|
|||
import 'package:halkyon_todo_flutter/src/feature/reminder/data/system_reminder_gateway.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/section/data/section_repository.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/section/data/sync_section_gateway.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/settings/data/settings_repository.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/settings/data/sync_settings_gateway.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/task/data/sync_task_gateway.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/task/data/task_repository.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
|
@ -27,7 +29,7 @@ import 'package:serverpod_flutter/serverpod_flutter.dart';
|
|||
import 'package:serverpod_offline_sync_client/offline_sync.dart';
|
||||
|
||||
/// A signed-in person's dependencies: who they are, their projects, sections,
|
||||
/// tasks and labels, on
|
||||
/// tasks, labels and settings, on
|
||||
/// their own local database in their folder, which the sync engine keeps in
|
||||
/// step with their other devices, every row sealed with their key (ADR 0033,
|
||||
/// D5, D6). halkyon_app's `PersonScope` opens and disposes them with the
|
||||
|
|
@ -51,6 +53,9 @@ class TodoPerson(
|
|||
/// Their labels, sealed and synced.
|
||||
required final LabelRepository labels,
|
||||
|
||||
/// Their settings of Todo's (Upcoming's Display), sealed and synced.
|
||||
required final SettingsRepository settings,
|
||||
|
||||
/// The device's notifications, where their tasks' reminders go (ADR
|
||||
/// 0033, D5); none in tests that don't say.
|
||||
final ReminderGateway reminders = const NoReminders(),
|
||||
|
|
@ -318,6 +323,7 @@ Future<TodoPerson> _openPersonData(
|
|||
final sections = SectionRepository(SyncSectionGateway(db, sync), keys);
|
||||
final tasks = TaskRepository(SyncTaskGateway(db, sync), keys);
|
||||
final labels = LabelRepository(SyncLabelGateway(db, sync), keys);
|
||||
final settings = SettingsRepository(SyncSettingsGateway(db, sync), keys);
|
||||
// The Inbox, made once by whichever device is first; nothing where this
|
||||
// device holds none of the account's keys.
|
||||
unawaited(
|
||||
|
|
@ -334,6 +340,7 @@ Future<TodoPerson> _openPersonData(
|
|||
sections: sections,
|
||||
tasks: tasks,
|
||||
labels: labels,
|
||||
settings: settings,
|
||||
reminders: reminders,
|
||||
onDispose: () async {
|
||||
try {
|
||||
|
|
@ -341,6 +348,7 @@ Future<TodoPerson> _openPersonData(
|
|||
await sections.dispose();
|
||||
await tasks.dispose();
|
||||
await labels.dispose();
|
||||
await settings.dispose();
|
||||
await sync.dispose();
|
||||
await db.close();
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -17,13 +17,14 @@ ByteData encodeProject(ProjectContent content) =>
|
|||
'color': ?content.color?.name,
|
||||
'position': content.position,
|
||||
// As at first, nothing: a project's Display is written once set (D17).
|
||||
if (!content.view.isDefault) 'view': _encodeView(content.view),
|
||||
if (!content.view.isDefault) 'view': encodeView(content.view),
|
||||
});
|
||||
|
||||
/// [view] by its values' names, those left as at first left out.
|
||||
Map<String, Object?> _encodeView(ProjectView view) => {
|
||||
Map<String, Object?> encodeView(ProjectView view) => {
|
||||
if (view.layout != ProjectLayout.list) 'layout': view.layout.name,
|
||||
if (view.completed) 'completed': true,
|
||||
if (view.occurrences) 'occurrences': true,
|
||||
if (view.grouping != TaskGrouping.none) 'grouping': view.grouping.name,
|
||||
if (view.sorting != TaskSorting.manual) 'sorting': view.sorting.name,
|
||||
if (view.date != DayRange.all) 'date': view.date.name,
|
||||
|
|
@ -34,7 +35,7 @@ Map<String, Object?> _encodeView(ProjectView view) => {
|
|||
};
|
||||
|
||||
/// The view [json] holds; a value this version doesn't know, as at first.
|
||||
ProjectView _decodeView(Object? json) {
|
||||
ProjectView decodeView(Object? json) {
|
||||
if (json is! Map<String, Object?>) {
|
||||
return const ProjectView();
|
||||
}
|
||||
|
|
@ -44,6 +45,7 @@ ProjectView _decodeView(Object? json) {
|
|||
return ProjectView(
|
||||
layout: named(ProjectLayout.values, 'layout', ProjectLayout.list),
|
||||
completed: json['completed'] == true,
|
||||
occurrences: json['occurrences'] == true,
|
||||
grouping: named(TaskGrouping.values, 'grouping', TaskGrouping.none),
|
||||
sorting: named(TaskSorting.values, 'sorting', TaskSorting.manual),
|
||||
date: named(DayRange.values, 'date', DayRange.all),
|
||||
|
|
@ -69,6 +71,6 @@ ProjectContent? decodeProject(ByteData payload) => decodeJson(
|
|||
name: json['name']! as String,
|
||||
color: PaletteColor.byName(json['color'] as String?),
|
||||
position: (json['position'] as num?)?.toDouble() ?? 0,
|
||||
view: _decodeView(json['view']),
|
||||
view: decodeView(json['view']),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
import 'package:halkyon_todo_flutter/src/feature/task/model/task.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/task/model/task_views.dart';
|
||||
|
||||
/// A row of a project's Calendar (ADR 0033, D17): a task on its own day,
|
||||
/// or, where `occurrence`, a later time of a repeating one, the task as it
|
||||
/// will be then (its ID kept, its date moved).
|
||||
typedef CalendarEntry = ({OpenTask task, bool occurrence});
|
||||
|
||||
/// What a project's Calendar shows (D17): the `overdue` tasks, then each of
|
||||
/// its `days` with its rows; the days with open tasks or later times are
|
||||
/// `busy`, marked in the week strip.
|
||||
typedef ProjectCalendar = ({
|
||||
List<OpenTask> overdue,
|
||||
List<(Day, List<CalendarEntry>)> days,
|
||||
Set<Day> busy,
|
||||
});
|
||||
|
||||
/// How many later times of one task at most, so that a daily one doesn't
|
||||
/// fill a year picked far ahead.
|
||||
const _occurrencesAtMost = 400;
|
||||
|
||||
/// [tasks] (those the project's Display keeps, done ones only where it
|
||||
/// shows them) as a Calendar from [today] on for [days] days (D17): as
|
||||
/// Upcoming's, overdue first, a day's tasks by time or [order]; where
|
||||
/// [occurrences], each repeating task not done also on the days it will
|
||||
/// come back, the first as if done today (`Repeat.next`), until its
|
||||
/// recurrence ends.
|
||||
ProjectCalendar calendarOf(
|
||||
List<OpenTask> tasks,
|
||||
Day today, {
|
||||
required int days,
|
||||
bool occurrences = false,
|
||||
int Function(OpenTask a, OpenTask b) order = byTime,
|
||||
}) {
|
||||
final (:overdue, days: listed) = upcomingOf(
|
||||
tasks,
|
||||
today,
|
||||
days: days,
|
||||
completed: true,
|
||||
);
|
||||
final byDay = {
|
||||
for (final (day, dayTasks) in listed)
|
||||
day: <CalendarEntry>[
|
||||
for (final task in dayTasks) (task: task, occurrence: false),
|
||||
],
|
||||
};
|
||||
final busy = {
|
||||
for (final task in tasks)
|
||||
if (task.content case TaskContent(
|
||||
completed: false,
|
||||
date: TaskDate(:final day),
|
||||
))
|
||||
day,
|
||||
};
|
||||
if (occurrences) {
|
||||
final end = today.plus(days);
|
||||
for (final task in tasks) {
|
||||
final content = task.content;
|
||||
if (content.completed) {
|
||||
continue;
|
||||
}
|
||||
if (content.date case final date? when date.repeat != null) {
|
||||
var repeat = date.repeat!;
|
||||
var next = repeat.next(date.day, today: today);
|
||||
for (
|
||||
var count = 0;
|
||||
next != null && next < end && count < _occurrencesAtMost;
|
||||
count++
|
||||
) {
|
||||
final day = next;
|
||||
repeat = repeat.counted();
|
||||
byDay[day]?.add((
|
||||
task: (
|
||||
id: task.id,
|
||||
content: content.copyWith(
|
||||
date: () => date.on(day).repeating(repeat),
|
||||
),
|
||||
),
|
||||
occurrence: true,
|
||||
));
|
||||
busy.add(day);
|
||||
next = repeat.next(day, today: day);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
overdue: overdue,
|
||||
days: [
|
||||
for (final MapEntry(:key, :value) in byDay.entries)
|
||||
(key, value..sort((a, b) => order(a.task, b.task))),
|
||||
],
|
||||
busy: busy,
|
||||
);
|
||||
}
|
||||
|
|
@ -63,12 +63,16 @@ enum DayRange() {
|
|||
}
|
||||
|
||||
/// A project's Display (ADR 0033, D17, as Todoist's): its [layout], whether
|
||||
/// it shows its done tasks, how it groups and sorts them, and the tasks it
|
||||
/// it shows its done tasks (and, as a Calendar, the later times of
|
||||
/// repeating ones), how it groups and sorts them, and the tasks it
|
||||
/// keeps, by date, deadline, priority and label. Kept in the project's
|
||||
/// sealed row, so every device of the person's shows the project alike.
|
||||
final class const ProjectView({
|
||||
final ProjectLayout layout = ProjectLayout.list,
|
||||
final bool completed = false,
|
||||
|
||||
/// Whether a Calendar shows repeating tasks on their later days too.
|
||||
final bool occurrences = false,
|
||||
final TaskGrouping grouping = TaskGrouping.none,
|
||||
final TaskSorting sorting = TaskSorting.manual,
|
||||
final DayRange date = DayRange.all,
|
||||
|
|
@ -140,6 +144,7 @@ final class const ProjectView({
|
|||
ProjectView copyWith({
|
||||
ProjectLayout? layout,
|
||||
bool? completed,
|
||||
bool? occurrences,
|
||||
TaskGrouping? grouping,
|
||||
TaskSorting? sorting,
|
||||
DayRange? date,
|
||||
|
|
@ -149,6 +154,7 @@ final class const ProjectView({
|
|||
}) => ProjectView(
|
||||
layout: layout ?? this.layout,
|
||||
completed: completed ?? this.completed,
|
||||
occurrences: occurrences ?? this.occurrences,
|
||||
grouping: grouping ?? this.grouping,
|
||||
sorting: sorting ?? this.sorting,
|
||||
date: date ?? this.date,
|
||||
|
|
@ -161,6 +167,7 @@ final class const ProjectView({
|
|||
List<Object?> get props => [
|
||||
layout,
|
||||
completed,
|
||||
occurrences,
|
||||
grouping,
|
||||
sorting,
|
||||
date,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:halkyon_todo_flutter/l10n/app_localizations.dart';
|
|||
import 'package:halkyon_todo_flutter/src/app/todo_dependencies.dart';
|
||||
import 'package:halkyon_todo_flutter/src/core/privacy/todo_keys.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/project/model/project_view.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/settings/model/todo_settings.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/task/bloc/todo_cubit.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/task/model/task.dart';
|
||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_pickers.dart';
|
||||
|
|
@ -12,52 +13,204 @@ import 'package:halkyon_ui/halkyon_ui.dart';
|
|||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// The button opening a project's Display (ADR 0033, D17), a dot on it
|
||||
/// while the project shows otherwise than at first.
|
||||
class const DisplayButton({required final String projectId, super.key})
|
||||
/// What a Display sheet sets (ADR 0033, D17): a project's view, or
|
||||
/// Upcoming's in the person's settings. [viewOf] reads its done tasks,
|
||||
/// future occurrences, sorting and filters, [write] keeps them, [layout]
|
||||
/// builds the rows above them (the layout's picker and what goes with
|
||||
/// it), and [reset] puts it all back as at first, which [isDefault] says
|
||||
/// it is.
|
||||
final class const DisplayTarget({
|
||||
required final ProjectView Function(TodoState state) viewOf,
|
||||
required final Future<void> Function(
|
||||
TodoPerson todo,
|
||||
TodoState state,
|
||||
ProjectView view,
|
||||
)
|
||||
write,
|
||||
required final bool Function(TodoState state) isDefault,
|
||||
required final Future<void> Function(TodoPerson todo, TodoState state) reset,
|
||||
required final List<Widget> Function(
|
||||
BuildContext context,
|
||||
TodoState state,
|
||||
Future<void> Function(Future<void> Function(TodoPerson todo) change) apply,
|
||||
)
|
||||
layout,
|
||||
|
||||
/// Whether it groups (a project's list and board).
|
||||
final bool Function(TodoState state) grouping = _never,
|
||||
|
||||
/// Whether it filters by date (a project; Upcoming is days already).
|
||||
final bool dateFilter = true,
|
||||
|
||||
/// Whether it shows repeating tasks' later times (a Calendar, Upcoming).
|
||||
final bool Function(TodoState state) occurrences = _never,
|
||||
|
||||
/// The sortings it offers; manual first, named [manualText].
|
||||
final List<TaskSorting> sortings = TaskSorting.values,
|
||||
final String Function(AppLocalizations l10n)? manualText,
|
||||
});
|
||||
|
||||
bool _never(TodoState state) => false;
|
||||
|
||||
/// A project's Display (D17): its layout (list, board, calendar), done
|
||||
/// tasks, grouping but in a Calendar, future occurrences in one, sorting,
|
||||
/// filters; kept in the project's sealed row.
|
||||
DisplayTarget projectDisplay(String projectId) {
|
||||
ProjectView viewOf(TodoState state) =>
|
||||
state.projectOf(projectId)?.content?.view ?? const ProjectView();
|
||||
Future<void> write(TodoPerson todo, TodoState state, ProjectView view) async {
|
||||
if (state.projectOf(projectId)?.content case final content?) {
|
||||
await todo.projects.update(projectId, content.copyWith(view: view));
|
||||
}
|
||||
}
|
||||
|
||||
bool calendar(TodoState state) =>
|
||||
viewOf(state).layout == ProjectLayout.calendar;
|
||||
return DisplayTarget(
|
||||
viewOf: viewOf,
|
||||
write: write,
|
||||
isDefault: (state) => viewOf(state).isDefault,
|
||||
reset: (todo, state) => write(todo, state, const ProjectView()),
|
||||
grouping: (state) => !calendar(state),
|
||||
occurrences: calendar,
|
||||
layout: (context, state, apply) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final view = viewOf(state);
|
||||
return [
|
||||
_LayoutPicker<ProjectLayout>(
|
||||
selected: view.layout,
|
||||
segments: [
|
||||
(ProjectLayout.list, LucideIcons.list, l10n.layoutList),
|
||||
(ProjectLayout.board, LucideIcons.squareKanban, l10n.layoutBoard),
|
||||
(
|
||||
ProjectLayout.calendar,
|
||||
LucideIcons.calendarDays,
|
||||
l10n.layoutCalendar,
|
||||
),
|
||||
],
|
||||
onPicked: (layout) => apply(
|
||||
(todo) => write(todo, state, view.copyWith(layout: layout)),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Upcoming's Display (D17, as Todoist's): agenda or 3-day, in the grid
|
||||
/// what tasks are coloured by, done tasks, future occurrences, sorting
|
||||
/// (smart: by time, then priority), and the filters by deadline, priority
|
||||
/// and label; kept in the person's settings.
|
||||
final upcomingDisplay = DisplayTarget(
|
||||
viewOf: (state) => state.settings.upcoming.filters,
|
||||
write: _writeUpcoming,
|
||||
isDefault: (state) => state.settings.upcoming.isDefault,
|
||||
reset: (todo, state) => todo.settings.write(
|
||||
state.settings.copyWith(upcoming: const UpcomingView()),
|
||||
),
|
||||
dateFilter: false,
|
||||
occurrences: (_) => true,
|
||||
sortings: const [
|
||||
TaskSorting.manual,
|
||||
TaskSorting.name,
|
||||
TaskSorting.deadline,
|
||||
TaskSorting.addedDate,
|
||||
TaskSorting.priority,
|
||||
],
|
||||
manualText: (l10n) => l10n.sortingSmart,
|
||||
layout: (context, state, apply) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final upcoming = state.settings.upcoming;
|
||||
Future<void> set(UpcomingView view) => apply(
|
||||
(todo) => todo.settings.write(state.settings.copyWith(upcoming: view)),
|
||||
);
|
||||
return [
|
||||
_LayoutPicker<UpcomingLayout>(
|
||||
selected: upcoming.layout,
|
||||
segments: [
|
||||
(UpcomingLayout.agenda, LucideIcons.list, l10n.layoutAgenda),
|
||||
(UpcomingLayout.threeDay, LucideIcons.columns3, l10n.layoutThreeDay),
|
||||
],
|
||||
onPicked: (layout) => set(upcoming.copyWith(layout: layout)),
|
||||
),
|
||||
if (upcoming.layout == UpcomingLayout.threeDay)
|
||||
ListTile(
|
||||
leading: const Icon(LucideIcons.palette),
|
||||
title: Text(l10n.displayColoring),
|
||||
subtitle: Text(coloringText(l10n, upcoming.coloring)),
|
||||
onTap: () async {
|
||||
if (await _pickOne(
|
||||
context,
|
||||
l10n.displayColoring,
|
||||
TaskColoring.values,
|
||||
upcoming.coloring,
|
||||
(coloring) => coloringText(l10n, coloring),
|
||||
)
|
||||
case final coloring?) {
|
||||
await set(upcoming.copyWith(coloring: coloring));
|
||||
}
|
||||
},
|
||||
),
|
||||
];
|
||||
},
|
||||
);
|
||||
|
||||
Future<void> _writeUpcoming(
|
||||
TodoPerson todo,
|
||||
TodoState state,
|
||||
ProjectView view,
|
||||
) => todo.settings.write(
|
||||
state.settings.copyWith(
|
||||
upcoming: state.settings.upcoming.copyWith(filters: view),
|
||||
),
|
||||
);
|
||||
|
||||
/// The button opening [target]'s Display (ADR 0033, D17), a dot on it
|
||||
/// while anything differs from how it shows at first.
|
||||
class const DisplayButton({required final DisplayTarget target, super.key})
|
||||
extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final view = context.select<TodoCubit, ProjectView>(
|
||||
(cubit) =>
|
||||
cubit.state.projectOf(projectId)?.content?.view ??
|
||||
const ProjectView(),
|
||||
final changed = context.select<TodoCubit, bool>(
|
||||
(cubit) => !target.isDefault(cubit.state),
|
||||
);
|
||||
return IconButton(
|
||||
tooltip: AppLocalizations.of(context).display,
|
||||
icon: Badge(
|
||||
smallSize: 8,
|
||||
isLabelVisible: !view.isDefault,
|
||||
isLabelVisible: changed,
|
||||
child: const Icon(LucideIcons.slidersHorizontal),
|
||||
),
|
||||
onPressed: () => showDisplaySheet(context, projectId),
|
||||
onPressed: () => showDisplaySheet(context, target),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A project's Display (D17, as Todoist's): its done tasks shown or not,
|
||||
/// how it groups and sorts, and what it filters by date, deadline, priority
|
||||
/// and label; each change written to the project at once, synced to every
|
||||
/// [target]'s Display (D17, as Todoist's): its layout, done tasks shown or
|
||||
/// not, how it groups and sorts, and what it filters by date, deadline,
|
||||
/// priority and label; each change written at once, synced to every
|
||||
/// device, Reset all putting it back as at first.
|
||||
Future<void> showDisplaySheet(BuildContext context, String projectId) =>
|
||||
Future<void> showDisplaySheet(BuildContext context, DisplayTarget target) =>
|
||||
showAdaptiveSheet<void>(
|
||||
context,
|
||||
builder: (context) => _DisplaySheet(projectId: projectId),
|
||||
builder: (context) => _DisplaySheet(target: target),
|
||||
);
|
||||
|
||||
class const _DisplaySheet({required final String projectId})
|
||||
class const _DisplaySheet({required final DisplayTarget target})
|
||||
extends StatelessWidget {
|
||||
Future<void> _set(BuildContext context, ProjectView view) async {
|
||||
/// Runs [change] with the person's data, saying so where this device
|
||||
/// can't seal.
|
||||
Future<void> _apply(
|
||||
BuildContext context,
|
||||
Future<void> Function(TodoPerson todo) change,
|
||||
) async {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final todo = PersonScope.of<TodoPerson>(context);
|
||||
final project = context.read<TodoCubit>().state.projectOf(projectId);
|
||||
if (project?.content case final content?) {
|
||||
try {
|
||||
await todo.projects.update(projectId, content.copyWith(view: view));
|
||||
} on KeysMissing {
|
||||
messenger.showSnackBar(SnackBar(content: Text(l10n.keysMissing)));
|
||||
}
|
||||
try {
|
||||
await change(todo);
|
||||
} on KeysMissing {
|
||||
messenger.showSnackBar(SnackBar(content: Text(l10n.keysMissing)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,8 +219,15 @@ class const _DisplaySheet({required final String projectId})
|
|||
final l10n = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final state = context.watch<TodoCubit>().state;
|
||||
final view =
|
||||
state.projectOf(projectId)?.content?.view ?? const ProjectView();
|
||||
final view = target.viewOf(state);
|
||||
Future<void> set(ProjectView view) =>
|
||||
_apply(context, (todo) => target.write(todo, state, view));
|
||||
Future<void> apply(Future<void> Function(TodoPerson todo) change) =>
|
||||
_apply(context, change);
|
||||
String sortingName(TaskSorting sorting) =>
|
||||
sorting == TaskSorting.manual && target.manualText != null
|
||||
? target.manualText!(l10n)
|
||||
: sortingText(l10n, sorting);
|
||||
final labels = {
|
||||
for (final label in state.labels)
|
||||
if (label.content case final content?) label.id: content.name,
|
||||
|
|
@ -99,99 +259,75 @@ class const _DisplaySheet({required final String projectId})
|
|||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
heading(l10n.layoutTitle),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: SegmentedButton<ProjectLayout>(
|
||||
showSelectedIcon: false,
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: ProjectLayout.list,
|
||||
icon: const Icon(LucideIcons.list),
|
||||
label: Text(l10n.layoutList),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: ProjectLayout.board,
|
||||
icon: const Icon(LucideIcons.squareKanban),
|
||||
label: Text(l10n.layoutBoard),
|
||||
),
|
||||
],
|
||||
// The calendar comes with its own step (D17); until then a
|
||||
// project laid out so shows as a list.
|
||||
selected: {
|
||||
if (view.layout == ProjectLayout.board)
|
||||
ProjectLayout.board
|
||||
else
|
||||
ProjectLayout.list,
|
||||
},
|
||||
onSelectionChanged: (picked) =>
|
||||
_set(context, view.copyWith(layout: picked.single)),
|
||||
),
|
||||
),
|
||||
...target.layout(context, state, apply),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(LucideIcons.circleCheck),
|
||||
title: Text(l10n.displayCompleted),
|
||||
value: view.completed,
|
||||
onChanged: (on) => _set(context, view.copyWith(completed: on)),
|
||||
onChanged: (on) => set(view.copyWith(completed: on)),
|
||||
),
|
||||
if (target.occurrences(state))
|
||||
SwitchListTile(
|
||||
secondary: const Icon(LucideIcons.repeat),
|
||||
title: Text(l10n.displayOccurrences),
|
||||
value: view.occurrences,
|
||||
onChanged: (on) => set(view.copyWith(occurrences: on)),
|
||||
),
|
||||
heading(l10n.displaySort),
|
||||
choice(
|
||||
LucideIcons.group,
|
||||
l10n.displayGrouping,
|
||||
groupingText(l10n, view.grouping),
|
||||
() async {
|
||||
if (await _pickOne(
|
||||
context,
|
||||
l10n.displayGrouping,
|
||||
TaskGrouping.values,
|
||||
view.grouping,
|
||||
(grouping) => groupingText(l10n, grouping),
|
||||
)
|
||||
case final grouping?) {
|
||||
if (context.mounted) {
|
||||
await _set(context, view.copyWith(grouping: grouping));
|
||||
if (target.grouping(state))
|
||||
choice(
|
||||
LucideIcons.group,
|
||||
l10n.displayGrouping,
|
||||
groupingText(l10n, view.grouping),
|
||||
() async {
|
||||
if (await _pickOne(
|
||||
context,
|
||||
l10n.displayGrouping,
|
||||
TaskGrouping.values,
|
||||
view.grouping,
|
||||
(grouping) => groupingText(l10n, grouping),
|
||||
)
|
||||
case final grouping?) {
|
||||
await set(view.copyWith(grouping: grouping));
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
choice(
|
||||
LucideIcons.arrowUpDown,
|
||||
l10n.displaySorting,
|
||||
sortingText(l10n, view.sorting),
|
||||
sortingName(view.sorting),
|
||||
() async {
|
||||
if (await _pickOne(
|
||||
context,
|
||||
l10n.displaySorting,
|
||||
TaskSorting.values,
|
||||
target.sortings,
|
||||
view.sorting,
|
||||
(sorting) => sortingText(l10n, sorting),
|
||||
sortingName,
|
||||
)
|
||||
case final sorting?) {
|
||||
if (context.mounted) {
|
||||
await _set(context, view.copyWith(sorting: sorting));
|
||||
}
|
||||
await set(view.copyWith(sorting: sorting));
|
||||
}
|
||||
},
|
||||
),
|
||||
heading(l10n.displayFilter),
|
||||
choice(
|
||||
LucideIcons.calendar,
|
||||
l10n.dateTitle,
|
||||
rangeText(l10n, view.date),
|
||||
() async {
|
||||
if (await _pickOne(
|
||||
context,
|
||||
l10n.dateTitle,
|
||||
DayRange.values,
|
||||
view.date,
|
||||
(range) => rangeText(l10n, range),
|
||||
)
|
||||
case final range?) {
|
||||
if (context.mounted) {
|
||||
await _set(context, view.copyWith(date: range));
|
||||
if (target.dateFilter)
|
||||
choice(
|
||||
LucideIcons.calendar,
|
||||
l10n.dateTitle,
|
||||
rangeText(l10n, view.date),
|
||||
() async {
|
||||
if (await _pickOne(
|
||||
context,
|
||||
l10n.dateTitle,
|
||||
DayRange.values,
|
||||
view.date,
|
||||
(range) => rangeText(l10n, range),
|
||||
)
|
||||
case final range?) {
|
||||
await set(view.copyWith(date: range));
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
choice(
|
||||
LucideIcons.target,
|
||||
l10n.displayDeadline,
|
||||
|
|
@ -205,9 +341,7 @@ class const _DisplaySheet({required final String projectId})
|
|||
(range) => rangeText(l10n, range, deadline: true),
|
||||
)
|
||||
case final range?) {
|
||||
if (context.mounted) {
|
||||
await _set(context, view.copyWith(deadline: range));
|
||||
}
|
||||
await set(view.copyWith(deadline: range));
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
@ -224,9 +358,7 @@ class const _DisplaySheet({required final String projectId})
|
|||
() async {
|
||||
if (await _pickPriorities(context, view.priorities)
|
||||
case final picked?) {
|
||||
if (context.mounted) {
|
||||
await _set(context, view.copyWith(priorities: picked));
|
||||
}
|
||||
await set(view.copyWith(priorities: picked));
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
@ -241,9 +373,7 @@ class const _DisplaySheet({required final String projectId})
|
|||
() async {
|
||||
if (await pickLabels(context, [...view.labelIds])
|
||||
case final picked?) {
|
||||
if (context.mounted) {
|
||||
await _set(context, view.copyWith(labelIds: {...picked}));
|
||||
}
|
||||
await set(view.copyWith(labelIds: {...picked}));
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
@ -252,9 +382,9 @@ class const _DisplaySheet({required final String projectId})
|
|||
footer: Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: view.isDefault
|
||||
onPressed: target.isDefault(state)
|
||||
? null
|
||||
: () => _set(context, const ProjectView()),
|
||||
: () => apply((todo) => target.reset(todo, state)),
|
||||
child: Text(l10n.displayReset),
|
||||
),
|
||||
const Spacer(),
|
||||
|
|
@ -268,6 +398,27 @@ class const _DisplaySheet({required final String projectId})
|
|||
}
|
||||
}
|
||||
|
||||
/// A Display's layouts side by side, [selected] marked.
|
||||
class const _LayoutPicker<T>({
|
||||
required final T selected,
|
||||
required final List<(T, IconData, String)> segments,
|
||||
required final ValueChanged<T> onPicked,
|
||||
}) extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: SegmentedButton<T>(
|
||||
showSelectedIcon: false,
|
||||
segments: [
|
||||
for (final (value, icon, label) in segments)
|
||||
ButtonSegment(value: value, icon: Icon(icon), label: Text(label)),
|
||||
],
|
||||
selected: {selected},
|
||||
onSelectionChanged: (picked) => onPicked(picked.single),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Asks for one of [values], [current] ticked.
|
||||
Future<T?> _pickOne<T>(
|
||||
BuildContext context,
|
||||
|
|
@ -362,6 +513,14 @@ String sortingText(AppLocalizations l10n, TaskSorting sorting) =>
|
|||
TaskSorting.priority => l10n.sortingPriority,
|
||||
};
|
||||
|
||||
/// [coloring]'s name.
|
||||
String coloringText(AppLocalizations l10n, TaskColoring coloring) =>
|
||||
switch (coloring) {
|
||||
TaskColoring.priority => l10n.priorityTitle,
|
||||
TaskColoring.label => l10n.displayLabel,
|
||||
TaskColoring.project => l10n.coloringProject,
|
||||
};
|
||||
|
||||
/// [range]'s name, for a filter by date or, where [deadline], by deadline.
|
||||
String rangeText(
|
||||
AppLocalizations l10n,
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class _ProjectBoardState() extends State<ProjectBoard> {
|
|||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
itemCount: count,
|
||||
separatorBuilder: (_, _) => const SizedBox(width: 12),
|
||||
separatorBuilder: (context, index) => const SizedBox(width: 12),
|
||||
itemBuilder: (context, index) =>
|
||||
SizedBox(width: 320, child: column(index)),
|
||||
);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue