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:
Tizian 「ティツィアーン」 2026-11-01 20:01:00 +01:00
commit 2939437c74
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 21845 additions and 314 deletions

View file

@ -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 'sync_tables.dart';
@ -370,6 +372,73 @@ class Protocol extends _is.DatabaseSerializationManager {
],
managed: true,
),
_isp.TableDefinition(
name: 'todo_settings',
dartName: 'TodoSettings',
schema: 'public',
module: 'halkyon_todo',
columns: [
_isp.ColumnDefinition(
name: 'id',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue?',
columnDefault: 'random_v7',
),
_isp.ColumnDefinition(
name: 'spaceId',
columnType: _isp.ColumnType.bigint,
isNullable: true,
dartType: 'int?',
),
_isp.ColumnDefinition(
name: 'keyId',
columnType: _isp.ColumnType.text,
isNullable: true,
dartType: 'String?',
),
_isp.ColumnDefinition(
name: 'payload',
columnType: _isp.ColumnType.bytea,
isNullable: false,
dartType: 'dart:typed_data:ByteData',
),
_isp.ColumnDefinition(
name: 'nonce',
columnType: _isp.ColumnType.bytea,
isNullable: true,
dartType: 'dart:typed_data:ByteData?',
),
],
foreignKeys: [
_isp.ForeignKeyDefinition(
constraintName: 'todo_settings_fk_0',
columns: ['spaceId'],
referenceTable: 'offline_sync_spaces',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.cascade,
matchType: null,
),
],
indexes: [
_isp.IndexDefinition(
indexName: 'todo_settings_space_idx',
tableSpace: null,
elements: [
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'spaceId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
],
managed: true,
),
..._ik8molhd.Protocol.targetTableDefinitions,
..._iais.Protocol.targetTableDefinitions,
..._izehhkf5.Protocol.targetTableDefinitions,
@ -417,6 +486,9 @@ class Protocol extends _is.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;
}
@ -432,6 +504,9 @@ class Protocol extends _is.DatabaseSerializationManager {
if (t == _is.getType<_imadkvi9.Section?>()) {
return (data != null ? _imadkvi9.Section.fromJson(data) : null) as T;
}
if (t == _is.getType<_icak60vd.TodoSettings?>()) {
return (data != null ? _icak60vd.TodoSettings.fromJson(data) : null) as T;
}
if (t == _is.getType<_ib4f8uyw.Task?>()) {
return (data != null ? _ib4f8uyw.Task.fromJson(data) : null) as T;
}
@ -462,6 +537,7 @@ class Protocol extends _is.DatabaseSerializationManager {
_imed3x5d.ContentKey => 'ContentKey',
_i9c44spp.Project => 'Project',
_imadkvi9.Section => 'Section',
_icak60vd.TodoSettings => 'TodoSettings',
_ib4f8uyw.Task => 'Task',
_ => null,
};
@ -488,6 +564,8 @@ class Protocol extends _is.DatabaseSerializationManager {
return 'Project';
case _imadkvi9.Section():
return 'Section';
case _icak60vd.TodoSettings():
return 'TodoSettings';
case _ib4f8uyw.Task():
return 'Task';
}
@ -542,6 +620,9 @@ class Protocol extends _is.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']);
}
@ -627,6 +708,8 @@ class Protocol extends _is.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;
}

View file

@ -0,0 +1,669 @@
/* 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/serverpod.dart' as _is;
/// 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 _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
TodoSettings._({
this.id,
this.spaceId,
this.keyId,
required this.payload,
this.nonce,
});
factory TodoSettings({
_is.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
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
spaceId: jsonSerialization['spaceId'] as int?,
keyId: jsonSerialization['keyId'] as String?,
payload: _is.ByteDataJsonExtension.fromJson(jsonSerialization['payload']),
nonce: jsonSerialization['nonce'] == null
? null
: _is.ByteDataJsonExtension.fromJson(jsonSerialization['nonce']),
);
}
static final t = TodoSettingsTable();
static const db = TodoSettingsRepository._();
@override
_is.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
_is.Table<_is.UuidValue?> get table => t;
/// Returns a shallow copy of this [TodoSettings]
/// with some or all fields replaced by the given arguments.
@_is.useResult
TodoSettings copyWith({
_is.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({
_is.WhereExpressionBuilder<TodoSettingsTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<TodoSettingsTable>? orderBy,
_is.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 _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _TodoSettingsImpl extends TodoSettings {
_TodoSettingsImpl({
_is.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.
@_is.useResult
@override
TodoSettings copyWith({
Object? id = _Undefined,
Object? spaceId = _Undefined,
Object? keyId = _Undefined,
_idt.ByteData? payload,
Object? nonce = _Undefined,
}) {
return TodoSettings(
id: id is _is.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 _is.UpdateTable<TodoSettingsTable> {
TodoSettingsUpdateTable(super.table);
_is.ColumnValue<int, int> spaceId(int? value) => _is.ColumnValue(
table.spaceId,
value,
);
_is.ColumnValue<String, String> keyId(String? value) => _is.ColumnValue(
table.keyId,
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> payload(_idt.ByteData value) =>
_is.ColumnValue(
table.payload,
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> nonce(_idt.ByteData? value) =>
_is.ColumnValue(
table.nonce,
value,
);
}
class TodoSettingsTable extends _is.Table<_is.UuidValue?> {
TodoSettingsTable({super.tableRelation}) : super(tableName: 'todo_settings') {
updateTable = TodoSettingsUpdateTable(this);
spaceId = _is.ColumnInt(
'spaceId',
this,
);
keyId = _is.ColumnString(
'keyId',
this,
);
payload = _is.ColumnByteData(
'payload',
this,
);
nonce = _is.ColumnByteData(
'nonce',
this,
);
}
late final TodoSettingsUpdateTable updateTable;
/// The space owning this row. Maintained by the sync engine.
late final _is.ColumnInt spaceId;
/// The key it is sealed with (ADR 0002's envelope); never null in Todo,
/// where everything is sealed.
late final _is.ColumnString keyId;
/// The content, sealed: UTF-8 JSON with a `schemaVersion` once opened.
late final _is.ColumnByteData payload;
/// The sealing's nonce (ADR 0002's envelope).
late final _is.ColumnByteData nonce;
@override
List<_is.Column> get columns => [
id,
spaceId,
keyId,
payload,
nonce,
];
}
class TodoSettingsInclude extends _is.IncludeObject {
TodoSettingsInclude._();
@override
Map<String, _is.Include?> get includes => {};
@override
_is.Table<_is.UuidValue?> get table => TodoSettings.t;
}
class TodoSettingsIncludeList extends _is.IncludeList {
TodoSettingsIncludeList._({
_is.WhereExpressionBuilder<TodoSettingsTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(TodoSettings.t);
}
@override
Map<String, _is.Include?> get includes => include?.includes ?? {};
@override
_is.Table<_is.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(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<TodoSettingsTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<TodoSettingsTable>? orderBy,
_is.OrderByListBuilder<TodoSettingsTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.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(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<TodoSettingsTable>? where,
int? offset,
_is.OrderByBuilder<TodoSettingsTable>? orderBy,
_is.OrderByListBuilder<TodoSettingsTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.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(
_is.DatabaseSession session,
_is.UuidValue id, {
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.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(
_is.DatabaseSession session,
List<TodoSettings> rows, {
_is.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(
_is.DatabaseSession session,
TodoSettings row, {
_is.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(
_is.DatabaseSession session,
List<TodoSettings> rows, {
required _is.ColumnSelections<TodoSettingsTable> conflictColumns,
_is.ColumnSelections<TodoSettingsTable>? updateColumns,
_is.WhereExpressionBuilder<TodoSettingsTable>? updateWhere,
_is.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(
_is.DatabaseSession session,
TodoSettings row, {
required _is.ColumnSelections<TodoSettingsTable> conflictColumns,
_is.ColumnSelections<TodoSettingsTable>? updateColumns,
_is.WhereExpressionBuilder<TodoSettingsTable>? updateWhere,
_is.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(
_is.DatabaseSession session,
List<TodoSettings> rows, {
_is.ColumnSelections<TodoSettingsTable>? columns,
_is.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(
_is.DatabaseSession session,
TodoSettings row, {
_is.ColumnSelections<TodoSettingsTable>? columns,
_is.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(
_is.DatabaseSession session,
_is.UuidValue id, {
required _is.ColumnValueListBuilder<TodoSettingsUpdateTable> columnValues,
_is.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(
_is.DatabaseSession session, {
required _is.ColumnValueListBuilder<TodoSettingsUpdateTable> columnValues,
required _is.WhereExpressionBuilder<TodoSettingsTable> where,
int? limit,
int? offset,
_is.OrderByBuilder<TodoSettingsTable>? orderBy,
_is.OrderByListBuilder<TodoSettingsTable>? orderByList,
_is.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(
_is.DatabaseSession session,
List<TodoSettings> rows, {
_is.OrderByBuilder<TodoSettingsTable>? orderBy,
_is.OrderByListBuilder<TodoSettingsTable>? orderByList,
_is.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(
_is.DatabaseSession session,
TodoSettings row, {
_is.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(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<TodoSettingsTable> where,
_is.OrderByBuilder<TodoSettingsTable>? orderBy,
_is.OrderByListBuilder<TodoSettingsTable>? orderByList,
_is.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(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<TodoSettingsTable>? where,
int? limit,
_is.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(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<TodoSettingsTable> where,
required _is.LockMode lockMode,
required _is.Transaction transaction,
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
}) async {
return session.db.lockRows<TodoSettings>(
where: where(TodoSettings.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}

View file

@ -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<_is.Table> syncTables = [
_i9c44spp.Project.t,
_imadkvi9.Section.t,
_ib4f8uyw.Task.t,
_icak60vd.TodoSettings.t,
];

View file

@ -0,0 +1,19 @@
### 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.
class: TodoSettings
table: todo_settings
database: sync
fields:
### The key it is sealed with (ADR 0002's envelope); never null in Todo,
### where everything is sealed.
keyId: String?
### The content, sealed: UTF-8 JSON with a `schemaVersion` once opened.
payload: ByteData
### The sealing's nonce (ADR 0002's envelope).
nonce: ByteData?
indexes:
# Deleting a space finds its rows through this index (see content_key).
todo_settings_space_idx:
fields: spaceId

View file

@ -7,7 +7,8 @@ import 'package:serverpod_offline_sync_server/serverpod_offline_sync_server.dart
/// device with a valid session would upload everything again) and before
/// their identities and the person go. Their personal space goes with
/// everything in it: the foreign keys cascade into every synced table (their
/// keys, projects, sections, tasks and labels) and the sync engine's bookkeeping.
/// keys, projects, sections, tasks, labels and settings) and the sync
/// engine's bookkeeping.
Future<AfterDeletion?> deleteTodoData(
Session session,
UuidValue authUserId,