tane/apps/app_seeds/lib/db/chat_database.g.dart
vjrj 68b04ea409 feat(chat): usable 1:1 chat — bottom-anchored, Drift-backed, dated
- Anchor the message list to the bottom (`reverse: true`) so new messages
  stay in view instead of landing below the fold.
- Move chat history from the OS keystore (O(n²) JSON blob, silent 200-msg
  cap) to a separate encrypted Drift/SQLCipher DB (`ChatDatabase`):
  indexed append, uncapped history, dedup as a unique-key invariant. It's
  an ephemeral per-device cache, isolated from the inventory schema, its
  migrations, and its sync. No data migration (pre-release).
- Add day separators (Today/Yesterday/locale date) and a per-bubble time,
  all via ICU (12/24h per locale; Localizations locale maps Asturian →
  Spanish for intl date symbols).
- Add peer avatars (deterministic colour from the pubkey + name initial),
  surface send failures that were previously silent, and make bubble text
  selectable (addresses, links).
- New i18n keys in en/es/pt/ast; tests for grouping, formatting, avatars,
  scroll anchoring, storage and send errors.

Docs: docs/design/chat-storage.md + open-decisions.md.
2026-07-11 06:40:50 +02:00

654 lines
20 KiB
Dart

// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat_database.dart';
// ignore_for_file: type=lint
class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$MessagesTable(this.attachedDatabase, [this._alias]);
static const VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id',
aliasedName,
false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'PRIMARY KEY AUTOINCREMENT',
),
);
static const VerificationMeta _accountScopeMeta = const VerificationMeta(
'accountScope',
);
@override
late final GeneratedColumn<String> accountScope = GeneratedColumn<String>(
'account_scope',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
static const VerificationMeta _peerPubkeyMeta = const VerificationMeta(
'peerPubkey',
);
@override
late final GeneratedColumn<String> peerPubkey = GeneratedColumn<String>(
'peer_pubkey',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
static const VerificationMeta _fromPubkeyMeta = const VerificationMeta(
'fromPubkey',
);
@override
late final GeneratedColumn<String> fromPubkey = GeneratedColumn<String>(
'from_pubkey',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
static const VerificationMeta _bodyMeta = const VerificationMeta('body');
@override
late final GeneratedColumn<String> body = GeneratedColumn<String>(
'body',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
static const VerificationMeta _sentAtMeta = const VerificationMeta('sentAt');
@override
late final GeneratedColumn<int> sentAt = GeneratedColumn<int>(
'sent_at',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: true,
);
@override
List<GeneratedColumn> get $columns => [
id,
accountScope,
peerPubkey,
fromPubkey,
body,
sentAt,
];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'messages';
@override
VerificationContext validateIntegrity(
Insertable<Message> instance, {
bool isInserting = false,
}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
}
if (data.containsKey('account_scope')) {
context.handle(
_accountScopeMeta,
accountScope.isAcceptableOrUnknown(
data['account_scope']!,
_accountScopeMeta,
),
);
} else if (isInserting) {
context.missing(_accountScopeMeta);
}
if (data.containsKey('peer_pubkey')) {
context.handle(
_peerPubkeyMeta,
peerPubkey.isAcceptableOrUnknown(data['peer_pubkey']!, _peerPubkeyMeta),
);
} else if (isInserting) {
context.missing(_peerPubkeyMeta);
}
if (data.containsKey('from_pubkey')) {
context.handle(
_fromPubkeyMeta,
fromPubkey.isAcceptableOrUnknown(data['from_pubkey']!, _fromPubkeyMeta),
);
} else if (isInserting) {
context.missing(_fromPubkeyMeta);
}
if (data.containsKey('body')) {
context.handle(
_bodyMeta,
body.isAcceptableOrUnknown(data['body']!, _bodyMeta),
);
} else if (isInserting) {
context.missing(_bodyMeta);
}
if (data.containsKey('sent_at')) {
context.handle(
_sentAtMeta,
sentAt.isAcceptableOrUnknown(data['sent_at']!, _sentAtMeta),
);
} else if (isInserting) {
context.missing(_sentAtMeta);
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [
{accountScope, peerPubkey, fromPubkey, sentAt, body},
];
@override
Message map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return Message(
id: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}id'],
)!,
accountScope: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}account_scope'],
)!,
peerPubkey: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}peer_pubkey'],
)!,
fromPubkey: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}from_pubkey'],
)!,
body: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}body'],
)!,
sentAt: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}sent_at'],
)!,
);
}
@override
$MessagesTable createAlias(String alias) {
return $MessagesTable(attachedDatabase, alias);
}
}
class Message extends DataClass implements Insertable<Message> {
final int id;
final String accountScope;
final String peerPubkey;
final String fromPubkey;
final String body;
/// Milliseconds since epoch — the message's own timestamp (NIP-17), used for
/// ordering and for the read/unread mark.
final int sentAt;
const Message({
required this.id,
required this.accountScope,
required this.peerPubkey,
required this.fromPubkey,
required this.body,
required this.sentAt,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['account_scope'] = Variable<String>(accountScope);
map['peer_pubkey'] = Variable<String>(peerPubkey);
map['from_pubkey'] = Variable<String>(fromPubkey);
map['body'] = Variable<String>(body);
map['sent_at'] = Variable<int>(sentAt);
return map;
}
MessagesCompanion toCompanion(bool nullToAbsent) {
return MessagesCompanion(
id: Value(id),
accountScope: Value(accountScope),
peerPubkey: Value(peerPubkey),
fromPubkey: Value(fromPubkey),
body: Value(body),
sentAt: Value(sentAt),
);
}
factory Message.fromJson(
Map<String, dynamic> json, {
ValueSerializer? serializer,
}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return Message(
id: serializer.fromJson<int>(json['id']),
accountScope: serializer.fromJson<String>(json['accountScope']),
peerPubkey: serializer.fromJson<String>(json['peerPubkey']),
fromPubkey: serializer.fromJson<String>(json['fromPubkey']),
body: serializer.fromJson<String>(json['body']),
sentAt: serializer.fromJson<int>(json['sentAt']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'accountScope': serializer.toJson<String>(accountScope),
'peerPubkey': serializer.toJson<String>(peerPubkey),
'fromPubkey': serializer.toJson<String>(fromPubkey),
'body': serializer.toJson<String>(body),
'sentAt': serializer.toJson<int>(sentAt),
};
}
Message copyWith({
int? id,
String? accountScope,
String? peerPubkey,
String? fromPubkey,
String? body,
int? sentAt,
}) => Message(
id: id ?? this.id,
accountScope: accountScope ?? this.accountScope,
peerPubkey: peerPubkey ?? this.peerPubkey,
fromPubkey: fromPubkey ?? this.fromPubkey,
body: body ?? this.body,
sentAt: sentAt ?? this.sentAt,
);
Message copyWithCompanion(MessagesCompanion data) {
return Message(
id: data.id.present ? data.id.value : this.id,
accountScope: data.accountScope.present
? data.accountScope.value
: this.accountScope,
peerPubkey: data.peerPubkey.present
? data.peerPubkey.value
: this.peerPubkey,
fromPubkey: data.fromPubkey.present
? data.fromPubkey.value
: this.fromPubkey,
body: data.body.present ? data.body.value : this.body,
sentAt: data.sentAt.present ? data.sentAt.value : this.sentAt,
);
}
@override
String toString() {
return (StringBuffer('Message(')
..write('id: $id, ')
..write('accountScope: $accountScope, ')
..write('peerPubkey: $peerPubkey, ')
..write('fromPubkey: $fromPubkey, ')
..write('body: $body, ')
..write('sentAt: $sentAt')
..write(')'))
.toString();
}
@override
int get hashCode =>
Object.hash(id, accountScope, peerPubkey, fromPubkey, body, sentAt);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is Message &&
other.id == this.id &&
other.accountScope == this.accountScope &&
other.peerPubkey == this.peerPubkey &&
other.fromPubkey == this.fromPubkey &&
other.body == this.body &&
other.sentAt == this.sentAt);
}
class MessagesCompanion extends UpdateCompanion<Message> {
final Value<int> id;
final Value<String> accountScope;
final Value<String> peerPubkey;
final Value<String> fromPubkey;
final Value<String> body;
final Value<int> sentAt;
const MessagesCompanion({
this.id = const Value.absent(),
this.accountScope = const Value.absent(),
this.peerPubkey = const Value.absent(),
this.fromPubkey = const Value.absent(),
this.body = const Value.absent(),
this.sentAt = const Value.absent(),
});
MessagesCompanion.insert({
this.id = const Value.absent(),
required String accountScope,
required String peerPubkey,
required String fromPubkey,
required String body,
required int sentAt,
}) : accountScope = Value(accountScope),
peerPubkey = Value(peerPubkey),
fromPubkey = Value(fromPubkey),
body = Value(body),
sentAt = Value(sentAt);
static Insertable<Message> custom({
Expression<int>? id,
Expression<String>? accountScope,
Expression<String>? peerPubkey,
Expression<String>? fromPubkey,
Expression<String>? body,
Expression<int>? sentAt,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (accountScope != null) 'account_scope': accountScope,
if (peerPubkey != null) 'peer_pubkey': peerPubkey,
if (fromPubkey != null) 'from_pubkey': fromPubkey,
if (body != null) 'body': body,
if (sentAt != null) 'sent_at': sentAt,
});
}
MessagesCompanion copyWith({
Value<int>? id,
Value<String>? accountScope,
Value<String>? peerPubkey,
Value<String>? fromPubkey,
Value<String>? body,
Value<int>? sentAt,
}) {
return MessagesCompanion(
id: id ?? this.id,
accountScope: accountScope ?? this.accountScope,
peerPubkey: peerPubkey ?? this.peerPubkey,
fromPubkey: fromPubkey ?? this.fromPubkey,
body: body ?? this.body,
sentAt: sentAt ?? this.sentAt,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (accountScope.present) {
map['account_scope'] = Variable<String>(accountScope.value);
}
if (peerPubkey.present) {
map['peer_pubkey'] = Variable<String>(peerPubkey.value);
}
if (fromPubkey.present) {
map['from_pubkey'] = Variable<String>(fromPubkey.value);
}
if (body.present) {
map['body'] = Variable<String>(body.value);
}
if (sentAt.present) {
map['sent_at'] = Variable<int>(sentAt.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('MessagesCompanion(')
..write('id: $id, ')
..write('accountScope: $accountScope, ')
..write('peerPubkey: $peerPubkey, ')
..write('fromPubkey: $fromPubkey, ')
..write('body: $body, ')
..write('sentAt: $sentAt')
..write(')'))
.toString();
}
}
abstract class _$ChatDatabase extends GeneratedDatabase {
_$ChatDatabase(QueryExecutor e) : super(e);
$ChatDatabaseManager get managers => $ChatDatabaseManager(this);
late final $MessagesTable messages = $MessagesTable(this);
late final Index messagesByConversation = Index(
'messages_by_conversation',
'CREATE INDEX messages_by_conversation ON messages (account_scope, peer_pubkey, sent_at)',
);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [
messages,
messagesByConversation,
];
}
typedef $$MessagesTableCreateCompanionBuilder =
MessagesCompanion Function({
Value<int> id,
required String accountScope,
required String peerPubkey,
required String fromPubkey,
required String body,
required int sentAt,
});
typedef $$MessagesTableUpdateCompanionBuilder =
MessagesCompanion Function({
Value<int> id,
Value<String> accountScope,
Value<String> peerPubkey,
Value<String> fromPubkey,
Value<String> body,
Value<int> sentAt,
});
class $$MessagesTableFilterComposer
extends Composer<_$ChatDatabase, $MessagesTable> {
$$MessagesTableFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnFilters<int> get id => $composableBuilder(
column: $table.id,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get accountScope => $composableBuilder(
column: $table.accountScope,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get peerPubkey => $composableBuilder(
column: $table.peerPubkey,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get fromPubkey => $composableBuilder(
column: $table.fromPubkey,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get body => $composableBuilder(
column: $table.body,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<int> get sentAt => $composableBuilder(
column: $table.sentAt,
builder: (column) => ColumnFilters(column),
);
}
class $$MessagesTableOrderingComposer
extends Composer<_$ChatDatabase, $MessagesTable> {
$$MessagesTableOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnOrderings<int> get id => $composableBuilder(
column: $table.id,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get accountScope => $composableBuilder(
column: $table.accountScope,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get peerPubkey => $composableBuilder(
column: $table.peerPubkey,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get fromPubkey => $composableBuilder(
column: $table.fromPubkey,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get body => $composableBuilder(
column: $table.body,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<int> get sentAt => $composableBuilder(
column: $table.sentAt,
builder: (column) => ColumnOrderings(column),
);
}
class $$MessagesTableAnnotationComposer
extends Composer<_$ChatDatabase, $MessagesTable> {
$$MessagesTableAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
GeneratedColumn<int> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
GeneratedColumn<String> get accountScope => $composableBuilder(
column: $table.accountScope,
builder: (column) => column,
);
GeneratedColumn<String> get peerPubkey => $composableBuilder(
column: $table.peerPubkey,
builder: (column) => column,
);
GeneratedColumn<String> get fromPubkey => $composableBuilder(
column: $table.fromPubkey,
builder: (column) => column,
);
GeneratedColumn<String> get body =>
$composableBuilder(column: $table.body, builder: (column) => column);
GeneratedColumn<int> get sentAt =>
$composableBuilder(column: $table.sentAt, builder: (column) => column);
}
class $$MessagesTableTableManager
extends
RootTableManager<
_$ChatDatabase,
$MessagesTable,
Message,
$$MessagesTableFilterComposer,
$$MessagesTableOrderingComposer,
$$MessagesTableAnnotationComposer,
$$MessagesTableCreateCompanionBuilder,
$$MessagesTableUpdateCompanionBuilder,
(Message, BaseReferences<_$ChatDatabase, $MessagesTable, Message>),
Message,
PrefetchHooks Function()
> {
$$MessagesTableTableManager(_$ChatDatabase db, $MessagesTable table)
: super(
TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
$$MessagesTableFilterComposer($db: db, $table: table),
createOrderingComposer: () =>
$$MessagesTableOrderingComposer($db: db, $table: table),
createComputedFieldComposer: () =>
$$MessagesTableAnnotationComposer($db: db, $table: table),
updateCompanionCallback:
({
Value<int> id = const Value.absent(),
Value<String> accountScope = const Value.absent(),
Value<String> peerPubkey = const Value.absent(),
Value<String> fromPubkey = const Value.absent(),
Value<String> body = const Value.absent(),
Value<int> sentAt = const Value.absent(),
}) => MessagesCompanion(
id: id,
accountScope: accountScope,
peerPubkey: peerPubkey,
fromPubkey: fromPubkey,
body: body,
sentAt: sentAt,
),
createCompanionCallback:
({
Value<int> id = const Value.absent(),
required String accountScope,
required String peerPubkey,
required String fromPubkey,
required String body,
required int sentAt,
}) => MessagesCompanion.insert(
id: id,
accountScope: accountScope,
peerPubkey: peerPubkey,
fromPubkey: fromPubkey,
body: body,
sentAt: sentAt,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
.toList(),
prefetchHooksCallback: null,
),
);
}
typedef $$MessagesTableProcessedTableManager =
ProcessedTableManager<
_$ChatDatabase,
$MessagesTable,
Message,
$$MessagesTableFilterComposer,
$$MessagesTableOrderingComposer,
$$MessagesTableAnnotationComposer,
$$MessagesTableCreateCompanionBuilder,
$$MessagesTableUpdateCompanionBuilder,
(Message, BaseReferences<_$ChatDatabase, $MessagesTable, Message>),
Message,
PrefetchHooks Function()
>;
class $ChatDatabaseManager {
final _$ChatDatabase _db;
$ChatDatabaseManager(this._db);
$$MessagesTableTableManager get messages =>
$$MessagesTableTableManager(_db, _db.messages);
}