Compare commits
5 Commits
73777fe74e
...
cb2de52bee
Author | SHA1 | Date | |
---|---|---|---|
cb2de52bee | |||
64e2644745 | |||
56711889ab | |||
4f47cd2c0c | |||
2b61c372f5 |
@ -753,5 +753,11 @@
|
|||||||
"accountBadges": "Badges",
|
"accountBadges": "Badges",
|
||||||
"accountBadgesDescription": "View and manage your badges.",
|
"accountBadgesDescription": "View and manage your badges.",
|
||||||
"badgeActivated": "Activated badge {}.",
|
"badgeActivated": "Activated badge {}.",
|
||||||
"viewDetailedAttachment": "Details"
|
"viewDetailedAttachment": "Details",
|
||||||
|
"screenKeyPairs": "Key Pairs",
|
||||||
|
"accountKeyPairs": "Key Pairs",
|
||||||
|
"accountKeyPairsDescription": "Manage the key pairs which used to encrypt messages.",
|
||||||
|
"enrollNewKeyPair": "Enroll New One",
|
||||||
|
"enrollNewKeyPairDescription": "Generate a new key pair.",
|
||||||
|
"keyPairHasPrivateKey": "With private key"
|
||||||
}
|
}
|
||||||
|
@ -751,5 +751,11 @@
|
|||||||
"accountBadges": "徽章",
|
"accountBadges": "徽章",
|
||||||
"accountBadgesDescription": "查看并管理你的徽章。",
|
"accountBadgesDescription": "查看并管理你的徽章。",
|
||||||
"badgeActivated": "已佩戴徽章 {}。",
|
"badgeActivated": "已佩戴徽章 {}。",
|
||||||
"viewDetailedAttachment": "查看附件详情"
|
"viewDetailedAttachment": "查看附件详情",
|
||||||
|
"screenKeyPairs": "密钥对",
|
||||||
|
"accountKeyPairs": "密钥对",
|
||||||
|
"accountKeyPairsDescription": "管理用于加密信息的密钥对。",
|
||||||
|
"enrollNewKeyPair": "新建密钥对",
|
||||||
|
"enrollNewKeyPairDescription": "生成一对新密钥对。",
|
||||||
|
"keyPairHasPrivateKey": "有私钥"
|
||||||
}
|
}
|
||||||
|
@ -751,5 +751,11 @@
|
|||||||
"accountBadges": "徽章",
|
"accountBadges": "徽章",
|
||||||
"accountBadgesDescription": "查看並管理你的徽章。",
|
"accountBadgesDescription": "查看並管理你的徽章。",
|
||||||
"badgeActivated": "已佩戴徽章 {}。",
|
"badgeActivated": "已佩戴徽章 {}。",
|
||||||
"viewDetailedAttachment": "查看附件詳情"
|
"viewDetailedAttachment": "查看附件詳情",
|
||||||
|
"screenKeyPairs": "密鑰對",
|
||||||
|
"accountKeyPairs": "密鑰對",
|
||||||
|
"accountKeyPairsDescription": "管理用於加密信息的密鑰對。",
|
||||||
|
"enrollNewKeyPair": "新建密鑰對",
|
||||||
|
"enrollNewKeyPairDescription": "生成一對新密鑰對,覆蓋當前的;如果已有一個密鑰將會丟棄舊密鑰的私鑰。",
|
||||||
|
"keyPairHasPrivateKey": "有私鑰"
|
||||||
}
|
}
|
||||||
|
@ -751,5 +751,11 @@
|
|||||||
"accountBadges": "徽章",
|
"accountBadges": "徽章",
|
||||||
"accountBadgesDescription": "查看並管理你的徽章。",
|
"accountBadgesDescription": "查看並管理你的徽章。",
|
||||||
"badgeActivated": "已佩戴徽章 {}。",
|
"badgeActivated": "已佩戴徽章 {}。",
|
||||||
"viewDetailedAttachment": "查看附件詳情"
|
"viewDetailedAttachment": "查看附件詳情",
|
||||||
|
"screenKeyPairs": "密鑰對",
|
||||||
|
"accountKeyPairs": "密鑰對",
|
||||||
|
"accountKeyPairsDescription": "管理用於加密信息的密鑰對。",
|
||||||
|
"enrollNewKeyPair": "新建密鑰對",
|
||||||
|
"enrollNewKeyPairDescription": "生成一對新密鑰對,覆蓋當前的;如果已有一個密鑰將會丟棄舊密鑰的私鑰。",
|
||||||
|
"keyPairHasPrivateKey": "有私鑰"
|
||||||
}
|
}
|
||||||
|
@ -4,4 +4,8 @@ targets:
|
|||||||
json_serializable:
|
json_serializable:
|
||||||
options:
|
options:
|
||||||
explicit_to_json: true
|
explicit_to_json: true
|
||||||
field_rename: snake
|
field_rename: snake
|
||||||
|
drift_dev:
|
||||||
|
options:
|
||||||
|
databases:
|
||||||
|
my_database: lib/database/database.dart
|
1
drift_schemas/my_database/drift_schema_v1.json
Normal file
1
drift_schemas/my_database/drift_schema_v1.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"sn_local_chat_channel","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"alias","getter_name":"alias","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"content","getter_name":"content","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SnChannelConverter()","dart_type_name":"SnChannel"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"sn_local_chat_message","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"channel_id","getter_name":"channelId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"content","getter_name":"content","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SnMessageConverter()","dart_type_name":"SnChatMessage"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]}
|
1
drift_schemas/my_database/drift_schema_v2.json
Normal file
1
drift_schemas/my_database/drift_schema_v2.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"sn_local_chat_channel","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"alias","getter_name":"alias","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"content","getter_name":"content","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SnChannelConverter()","dart_type_name":"SnChannel"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"sn_local_chat_message","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"channel_id","getter_name":"channelId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"content","getter_name":"content","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SnMessageConverter()","dart_type_name":"SnChatMessage"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":2,"references":[],"type":"table","data":{"name":"sn_local_key_pair","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"account_id","getter_name":"accountId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"public_key","getter_name":"publicKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_active","getter_name":"isActive","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_active\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_active\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]}
|
@ -37,6 +37,8 @@ PODS:
|
|||||||
- DKPhotoGallery/Resource (0.0.19):
|
- DKPhotoGallery/Resource (0.0.19):
|
||||||
- SDWebImage
|
- SDWebImage
|
||||||
- SwiftyGif
|
- SwiftyGif
|
||||||
|
- fast_rsa (0.6.0):
|
||||||
|
- Flutter
|
||||||
- file_picker (0.0.1):
|
- file_picker (0.0.1):
|
||||||
- DKImagePickerController/PhotoGallery
|
- DKImagePickerController/PhotoGallery
|
||||||
- Flutter
|
- Flutter
|
||||||
@ -262,6 +264,7 @@ DEPENDENCIES:
|
|||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- croppy (from `.symlinks/plugins/croppy/ios`)
|
- croppy (from `.symlinks/plugins/croppy/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
|
- fast_rsa (from `.symlinks/plugins/fast_rsa/ios`)
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- file_saver (from `.symlinks/plugins/file_saver/ios`)
|
- file_saver (from `.symlinks/plugins/file_saver/ios`)
|
||||||
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
|
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
|
||||||
@ -331,6 +334,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/croppy/ios"
|
:path: ".symlinks/plugins/croppy/ios"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
|
fast_rsa:
|
||||||
|
:path: ".symlinks/plugins/fast_rsa/ios"
|
||||||
file_picker:
|
file_picker:
|
||||||
:path: ".symlinks/plugins/file_picker/ios"
|
:path: ".symlinks/plugins/file_picker/ios"
|
||||||
file_saver:
|
file_saver:
|
||||||
@ -411,6 +416,7 @@ SPEC CHECKSUMS:
|
|||||||
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
||||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||||
|
fast_rsa: dc48fb05f26bb108863de122b2a9f5554e8e2591
|
||||||
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
|
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
|
||||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||||
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
|
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
|
||||||
|
@ -2,16 +2,18 @@ import 'package:drift/drift.dart';
|
|||||||
import 'package:drift_flutter/drift_flutter.dart';
|
import 'package:drift_flutter/drift_flutter.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:surface/database/chat.dart';
|
import 'package:surface/database/chat.dart';
|
||||||
|
import 'package:surface/database/database.steps.dart';
|
||||||
|
import 'package:surface/database/keypair.dart';
|
||||||
import 'package:surface/types/chat.dart';
|
import 'package:surface/types/chat.dart';
|
||||||
|
|
||||||
part 'database.g.dart';
|
part 'database.g.dart';
|
||||||
|
|
||||||
@DriftDatabase(tables: [SnLocalChatChannel, SnLocalChatMessage])
|
@DriftDatabase(tables: [SnLocalChatChannel, SnLocalChatMessage, SnLocalKeyPair])
|
||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
AppDatabase() : super(_openConnection());
|
AppDatabase([QueryExecutor? e]) : super(e ?? _openConnection());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 1;
|
int get schemaVersion => 2;
|
||||||
|
|
||||||
static QueryExecutor _openConnection() {
|
static QueryExecutor _openConnection() {
|
||||||
return driftDatabase(
|
return driftDatabase(
|
||||||
@ -25,4 +27,13 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MigrationStrategy get migration {
|
||||||
|
return MigrationStrategy(
|
||||||
|
onUpgrade: stepByStep(from1To2: (m, schema) async {
|
||||||
|
// Nothing else to do here
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -538,6 +538,322 @@ class SnLocalChatMessageCompanion
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class $SnLocalKeyPairTable extends SnLocalKeyPair
|
||||||
|
with TableInfo<$SnLocalKeyPairTable, SnLocalKeyPairData> {
|
||||||
|
@override
|
||||||
|
final GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$SnLocalKeyPairTable(this.attachedDatabase, [this._alias]);
|
||||||
|
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<String> id = GeneratedColumn<String>(
|
||||||
|
'id', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
static const VerificationMeta _accountIdMeta =
|
||||||
|
const VerificationMeta('accountId');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<int> accountId = GeneratedColumn<int>(
|
||||||
|
'account_id', aliasedName, false,
|
||||||
|
type: DriftSqlType.int, requiredDuringInsert: true);
|
||||||
|
static const VerificationMeta _publicKeyMeta =
|
||||||
|
const VerificationMeta('publicKey');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<String> publicKey = GeneratedColumn<String>(
|
||||||
|
'public_key', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
static const VerificationMeta _privateKeyMeta =
|
||||||
|
const VerificationMeta('privateKey');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<String> privateKey = GeneratedColumn<String>(
|
||||||
|
'private_key', aliasedName, true,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: false);
|
||||||
|
static const VerificationMeta _isActiveMeta =
|
||||||
|
const VerificationMeta('isActive');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<bool> isActive = GeneratedColumn<bool>(
|
||||||
|
'is_active', aliasedName, false,
|
||||||
|
type: DriftSqlType.bool,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints:
|
||||||
|
GeneratedColumn.constraintIsAlways('CHECK ("is_active" IN (0, 1))'),
|
||||||
|
defaultValue: Constant(false));
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns =>
|
||||||
|
[id, accountId, publicKey, privateKey, isActive];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'sn_local_key_pair';
|
||||||
|
@override
|
||||||
|
VerificationContext validateIntegrity(Insertable<SnLocalKeyPairData> instance,
|
||||||
|
{bool isInserting = false}) {
|
||||||
|
final context = VerificationContext();
|
||||||
|
final data = instance.toColumns(true);
|
||||||
|
if (data.containsKey('id')) {
|
||||||
|
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_idMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('account_id')) {
|
||||||
|
context.handle(_accountIdMeta,
|
||||||
|
accountId.isAcceptableOrUnknown(data['account_id']!, _accountIdMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_accountIdMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('public_key')) {
|
||||||
|
context.handle(_publicKeyMeta,
|
||||||
|
publicKey.isAcceptableOrUnknown(data['public_key']!, _publicKeyMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_publicKeyMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('private_key')) {
|
||||||
|
context.handle(
|
||||||
|
_privateKeyMeta,
|
||||||
|
privateKey.isAcceptableOrUnknown(
|
||||||
|
data['private_key']!, _privateKeyMeta));
|
||||||
|
}
|
||||||
|
if (data.containsKey('is_active')) {
|
||||||
|
context.handle(_isActiveMeta,
|
||||||
|
isActive.isAcceptableOrUnknown(data['is_active']!, _isActiveMeta));
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<GeneratedColumn> get $primaryKey => const {};
|
||||||
|
@override
|
||||||
|
SnLocalKeyPairData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return SnLocalKeyPairData(
|
||||||
|
id: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||||
|
accountId: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}account_id'])!,
|
||||||
|
publicKey: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}public_key'])!,
|
||||||
|
privateKey: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}private_key']),
|
||||||
|
isActive: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.bool, data['${effectivePrefix}is_active'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$SnLocalKeyPairTable createAlias(String alias) {
|
||||||
|
return $SnLocalKeyPairTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalKeyPairData extends DataClass
|
||||||
|
implements Insertable<SnLocalKeyPairData> {
|
||||||
|
final String id;
|
||||||
|
final int accountId;
|
||||||
|
final String publicKey;
|
||||||
|
final String? privateKey;
|
||||||
|
final bool isActive;
|
||||||
|
const SnLocalKeyPairData(
|
||||||
|
{required this.id,
|
||||||
|
required this.accountId,
|
||||||
|
required this.publicKey,
|
||||||
|
this.privateKey,
|
||||||
|
required this.isActive});
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
map['id'] = Variable<String>(id);
|
||||||
|
map['account_id'] = Variable<int>(accountId);
|
||||||
|
map['public_key'] = Variable<String>(publicKey);
|
||||||
|
if (!nullToAbsent || privateKey != null) {
|
||||||
|
map['private_key'] = Variable<String>(privateKey);
|
||||||
|
}
|
||||||
|
map['is_active'] = Variable<bool>(isActive);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalKeyPairCompanion toCompanion(bool nullToAbsent) {
|
||||||
|
return SnLocalKeyPairCompanion(
|
||||||
|
id: Value(id),
|
||||||
|
accountId: Value(accountId),
|
||||||
|
publicKey: Value(publicKey),
|
||||||
|
privateKey: privateKey == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(privateKey),
|
||||||
|
isActive: Value(isActive),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SnLocalKeyPairData.fromJson(Map<String, dynamic> json,
|
||||||
|
{ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return SnLocalKeyPairData(
|
||||||
|
id: serializer.fromJson<String>(json['id']),
|
||||||
|
accountId: serializer.fromJson<int>(json['accountId']),
|
||||||
|
publicKey: serializer.fromJson<String>(json['publicKey']),
|
||||||
|
privateKey: serializer.fromJson<String?>(json['privateKey']),
|
||||||
|
isActive: serializer.fromJson<bool>(json['isActive']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'id': serializer.toJson<String>(id),
|
||||||
|
'accountId': serializer.toJson<int>(accountId),
|
||||||
|
'publicKey': serializer.toJson<String>(publicKey),
|
||||||
|
'privateKey': serializer.toJson<String?>(privateKey),
|
||||||
|
'isActive': serializer.toJson<bool>(isActive),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalKeyPairData copyWith(
|
||||||
|
{String? id,
|
||||||
|
int? accountId,
|
||||||
|
String? publicKey,
|
||||||
|
Value<String?> privateKey = const Value.absent(),
|
||||||
|
bool? isActive}) =>
|
||||||
|
SnLocalKeyPairData(
|
||||||
|
id: id ?? this.id,
|
||||||
|
accountId: accountId ?? this.accountId,
|
||||||
|
publicKey: publicKey ?? this.publicKey,
|
||||||
|
privateKey: privateKey.present ? privateKey.value : this.privateKey,
|
||||||
|
isActive: isActive ?? this.isActive,
|
||||||
|
);
|
||||||
|
SnLocalKeyPairData copyWithCompanion(SnLocalKeyPairCompanion data) {
|
||||||
|
return SnLocalKeyPairData(
|
||||||
|
id: data.id.present ? data.id.value : this.id,
|
||||||
|
accountId: data.accountId.present ? data.accountId.value : this.accountId,
|
||||||
|
publicKey: data.publicKey.present ? data.publicKey.value : this.publicKey,
|
||||||
|
privateKey:
|
||||||
|
data.privateKey.present ? data.privateKey.value : this.privateKey,
|
||||||
|
isActive: data.isActive.present ? data.isActive.value : this.isActive,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalKeyPairData(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('accountId: $accountId, ')
|
||||||
|
..write('publicKey: $publicKey, ')
|
||||||
|
..write('privateKey: $privateKey, ')
|
||||||
|
..write('isActive: $isActive')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
Object.hash(id, accountId, publicKey, privateKey, isActive);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is SnLocalKeyPairData &&
|
||||||
|
other.id == this.id &&
|
||||||
|
other.accountId == this.accountId &&
|
||||||
|
other.publicKey == this.publicKey &&
|
||||||
|
other.privateKey == this.privateKey &&
|
||||||
|
other.isActive == this.isActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalKeyPairCompanion extends UpdateCompanion<SnLocalKeyPairData> {
|
||||||
|
final Value<String> id;
|
||||||
|
final Value<int> accountId;
|
||||||
|
final Value<String> publicKey;
|
||||||
|
final Value<String?> privateKey;
|
||||||
|
final Value<bool> isActive;
|
||||||
|
final Value<int> rowid;
|
||||||
|
const SnLocalKeyPairCompanion({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
this.accountId = const Value.absent(),
|
||||||
|
this.publicKey = const Value.absent(),
|
||||||
|
this.privateKey = const Value.absent(),
|
||||||
|
this.isActive = const Value.absent(),
|
||||||
|
this.rowid = const Value.absent(),
|
||||||
|
});
|
||||||
|
SnLocalKeyPairCompanion.insert({
|
||||||
|
required String id,
|
||||||
|
required int accountId,
|
||||||
|
required String publicKey,
|
||||||
|
this.privateKey = const Value.absent(),
|
||||||
|
this.isActive = const Value.absent(),
|
||||||
|
this.rowid = const Value.absent(),
|
||||||
|
}) : id = Value(id),
|
||||||
|
accountId = Value(accountId),
|
||||||
|
publicKey = Value(publicKey);
|
||||||
|
static Insertable<SnLocalKeyPairData> custom({
|
||||||
|
Expression<String>? id,
|
||||||
|
Expression<int>? accountId,
|
||||||
|
Expression<String>? publicKey,
|
||||||
|
Expression<String>? privateKey,
|
||||||
|
Expression<bool>? isActive,
|
||||||
|
Expression<int>? rowid,
|
||||||
|
}) {
|
||||||
|
return RawValuesInsertable({
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (accountId != null) 'account_id': accountId,
|
||||||
|
if (publicKey != null) 'public_key': publicKey,
|
||||||
|
if (privateKey != null) 'private_key': privateKey,
|
||||||
|
if (isActive != null) 'is_active': isActive,
|
||||||
|
if (rowid != null) 'rowid': rowid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalKeyPairCompanion copyWith(
|
||||||
|
{Value<String>? id,
|
||||||
|
Value<int>? accountId,
|
||||||
|
Value<String>? publicKey,
|
||||||
|
Value<String?>? privateKey,
|
||||||
|
Value<bool>? isActive,
|
||||||
|
Value<int>? rowid}) {
|
||||||
|
return SnLocalKeyPairCompanion(
|
||||||
|
id: id ?? this.id,
|
||||||
|
accountId: accountId ?? this.accountId,
|
||||||
|
publicKey: publicKey ?? this.publicKey,
|
||||||
|
privateKey: privateKey ?? this.privateKey,
|
||||||
|
isActive: isActive ?? this.isActive,
|
||||||
|
rowid: rowid ?? this.rowid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
if (id.present) {
|
||||||
|
map['id'] = Variable<String>(id.value);
|
||||||
|
}
|
||||||
|
if (accountId.present) {
|
||||||
|
map['account_id'] = Variable<int>(accountId.value);
|
||||||
|
}
|
||||||
|
if (publicKey.present) {
|
||||||
|
map['public_key'] = Variable<String>(publicKey.value);
|
||||||
|
}
|
||||||
|
if (privateKey.present) {
|
||||||
|
map['private_key'] = Variable<String>(privateKey.value);
|
||||||
|
}
|
||||||
|
if (isActive.present) {
|
||||||
|
map['is_active'] = Variable<bool>(isActive.value);
|
||||||
|
}
|
||||||
|
if (rowid.present) {
|
||||||
|
map['rowid'] = Variable<int>(rowid.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalKeyPairCompanion(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('accountId: $accountId, ')
|
||||||
|
..write('publicKey: $publicKey, ')
|
||||||
|
..write('privateKey: $privateKey, ')
|
||||||
|
..write('isActive: $isActive, ')
|
||||||
|
..write('rowid: $rowid')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract class _$AppDatabase extends GeneratedDatabase {
|
abstract class _$AppDatabase extends GeneratedDatabase {
|
||||||
_$AppDatabase(QueryExecutor e) : super(e);
|
_$AppDatabase(QueryExecutor e) : super(e);
|
||||||
$AppDatabaseManager get managers => $AppDatabaseManager(this);
|
$AppDatabaseManager get managers => $AppDatabaseManager(this);
|
||||||
@ -545,12 +861,13 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
$SnLocalChatChannelTable(this);
|
$SnLocalChatChannelTable(this);
|
||||||
late final $SnLocalChatMessageTable snLocalChatMessage =
|
late final $SnLocalChatMessageTable snLocalChatMessage =
|
||||||
$SnLocalChatMessageTable(this);
|
$SnLocalChatMessageTable(this);
|
||||||
|
late final $SnLocalKeyPairTable snLocalKeyPair = $SnLocalKeyPairTable(this);
|
||||||
@override
|
@override
|
||||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||||
@override
|
@override
|
||||||
List<DatabaseSchemaEntity> get allSchemaEntities =>
|
List<DatabaseSchemaEntity> get allSchemaEntities =>
|
||||||
[snLocalChatChannel, snLocalChatMessage];
|
[snLocalChatChannel, snLocalChatMessage, snLocalKeyPair];
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef $$SnLocalChatChannelTableCreateCompanionBuilder
|
typedef $$SnLocalChatChannelTableCreateCompanionBuilder
|
||||||
@ -869,6 +1186,180 @@ typedef $$SnLocalChatMessageTableProcessedTableManager = ProcessedTableManager<
|
|||||||
),
|
),
|
||||||
SnLocalChatMessageData,
|
SnLocalChatMessageData,
|
||||||
PrefetchHooks Function()>;
|
PrefetchHooks Function()>;
|
||||||
|
typedef $$SnLocalKeyPairTableCreateCompanionBuilder = SnLocalKeyPairCompanion
|
||||||
|
Function({
|
||||||
|
required String id,
|
||||||
|
required int accountId,
|
||||||
|
required String publicKey,
|
||||||
|
Value<String?> privateKey,
|
||||||
|
Value<bool> isActive,
|
||||||
|
Value<int> rowid,
|
||||||
|
});
|
||||||
|
typedef $$SnLocalKeyPairTableUpdateCompanionBuilder = SnLocalKeyPairCompanion
|
||||||
|
Function({
|
||||||
|
Value<String> id,
|
||||||
|
Value<int> accountId,
|
||||||
|
Value<String> publicKey,
|
||||||
|
Value<String?> privateKey,
|
||||||
|
Value<bool> isActive,
|
||||||
|
Value<int> rowid,
|
||||||
|
});
|
||||||
|
|
||||||
|
class $$SnLocalKeyPairTableFilterComposer
|
||||||
|
extends Composer<_$AppDatabase, $SnLocalKeyPairTable> {
|
||||||
|
$$SnLocalKeyPairTableFilterComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
ColumnFilters<String> get id => $composableBuilder(
|
||||||
|
column: $table.id, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<int> get accountId => $composableBuilder(
|
||||||
|
column: $table.accountId, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<String> get publicKey => $composableBuilder(
|
||||||
|
column: $table.publicKey, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<String> get privateKey => $composableBuilder(
|
||||||
|
column: $table.privateKey, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<bool> get isActive => $composableBuilder(
|
||||||
|
column: $table.isActive, builder: (column) => ColumnFilters(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$SnLocalKeyPairTableOrderingComposer
|
||||||
|
extends Composer<_$AppDatabase, $SnLocalKeyPairTable> {
|
||||||
|
$$SnLocalKeyPairTableOrderingComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
ColumnOrderings<String> get id => $composableBuilder(
|
||||||
|
column: $table.id, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<int> get accountId => $composableBuilder(
|
||||||
|
column: $table.accountId, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<String> get publicKey => $composableBuilder(
|
||||||
|
column: $table.publicKey, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<String> get privateKey => $composableBuilder(
|
||||||
|
column: $table.privateKey, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<bool> get isActive => $composableBuilder(
|
||||||
|
column: $table.isActive, builder: (column) => ColumnOrderings(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$SnLocalKeyPairTableAnnotationComposer
|
||||||
|
extends Composer<_$AppDatabase, $SnLocalKeyPairTable> {
|
||||||
|
$$SnLocalKeyPairTableAnnotationComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
GeneratedColumn<String> get id =>
|
||||||
|
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<int> get accountId =>
|
||||||
|
$composableBuilder(column: $table.accountId, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<String> get publicKey =>
|
||||||
|
$composableBuilder(column: $table.publicKey, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<String> get privateKey => $composableBuilder(
|
||||||
|
column: $table.privateKey, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<bool> get isActive =>
|
||||||
|
$composableBuilder(column: $table.isActive, builder: (column) => column);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$SnLocalKeyPairTableTableManager extends RootTableManager<
|
||||||
|
_$AppDatabase,
|
||||||
|
$SnLocalKeyPairTable,
|
||||||
|
SnLocalKeyPairData,
|
||||||
|
$$SnLocalKeyPairTableFilterComposer,
|
||||||
|
$$SnLocalKeyPairTableOrderingComposer,
|
||||||
|
$$SnLocalKeyPairTableAnnotationComposer,
|
||||||
|
$$SnLocalKeyPairTableCreateCompanionBuilder,
|
||||||
|
$$SnLocalKeyPairTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
SnLocalKeyPairData,
|
||||||
|
BaseReferences<_$AppDatabase, $SnLocalKeyPairTable, SnLocalKeyPairData>
|
||||||
|
),
|
||||||
|
SnLocalKeyPairData,
|
||||||
|
PrefetchHooks Function()> {
|
||||||
|
$$SnLocalKeyPairTableTableManager(
|
||||||
|
_$AppDatabase db, $SnLocalKeyPairTable table)
|
||||||
|
: super(TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
createFilteringComposer: () =>
|
||||||
|
$$SnLocalKeyPairTableFilterComposer($db: db, $table: table),
|
||||||
|
createOrderingComposer: () =>
|
||||||
|
$$SnLocalKeyPairTableOrderingComposer($db: db, $table: table),
|
||||||
|
createComputedFieldComposer: () =>
|
||||||
|
$$SnLocalKeyPairTableAnnotationComposer($db: db, $table: table),
|
||||||
|
updateCompanionCallback: ({
|
||||||
|
Value<String> id = const Value.absent(),
|
||||||
|
Value<int> accountId = const Value.absent(),
|
||||||
|
Value<String> publicKey = const Value.absent(),
|
||||||
|
Value<String?> privateKey = const Value.absent(),
|
||||||
|
Value<bool> isActive = const Value.absent(),
|
||||||
|
Value<int> rowid = const Value.absent(),
|
||||||
|
}) =>
|
||||||
|
SnLocalKeyPairCompanion(
|
||||||
|
id: id,
|
||||||
|
accountId: accountId,
|
||||||
|
publicKey: publicKey,
|
||||||
|
privateKey: privateKey,
|
||||||
|
isActive: isActive,
|
||||||
|
rowid: rowid,
|
||||||
|
),
|
||||||
|
createCompanionCallback: ({
|
||||||
|
required String id,
|
||||||
|
required int accountId,
|
||||||
|
required String publicKey,
|
||||||
|
Value<String?> privateKey = const Value.absent(),
|
||||||
|
Value<bool> isActive = const Value.absent(),
|
||||||
|
Value<int> rowid = const Value.absent(),
|
||||||
|
}) =>
|
||||||
|
SnLocalKeyPairCompanion.insert(
|
||||||
|
id: id,
|
||||||
|
accountId: accountId,
|
||||||
|
publicKey: publicKey,
|
||||||
|
privateKey: privateKey,
|
||||||
|
isActive: isActive,
|
||||||
|
rowid: rowid,
|
||||||
|
),
|
||||||
|
withReferenceMapper: (p0) => p0
|
||||||
|
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||||
|
.toList(),
|
||||||
|
prefetchHooksCallback: null,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef $$SnLocalKeyPairTableProcessedTableManager = ProcessedTableManager<
|
||||||
|
_$AppDatabase,
|
||||||
|
$SnLocalKeyPairTable,
|
||||||
|
SnLocalKeyPairData,
|
||||||
|
$$SnLocalKeyPairTableFilterComposer,
|
||||||
|
$$SnLocalKeyPairTableOrderingComposer,
|
||||||
|
$$SnLocalKeyPairTableAnnotationComposer,
|
||||||
|
$$SnLocalKeyPairTableCreateCompanionBuilder,
|
||||||
|
$$SnLocalKeyPairTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
SnLocalKeyPairData,
|
||||||
|
BaseReferences<_$AppDatabase, $SnLocalKeyPairTable, SnLocalKeyPairData>
|
||||||
|
),
|
||||||
|
SnLocalKeyPairData,
|
||||||
|
PrefetchHooks Function()>;
|
||||||
|
|
||||||
class $AppDatabaseManager {
|
class $AppDatabaseManager {
|
||||||
final _$AppDatabase _db;
|
final _$AppDatabase _db;
|
||||||
@ -877,4 +1368,6 @@ class $AppDatabaseManager {
|
|||||||
$$SnLocalChatChannelTableTableManager(_db, _db.snLocalChatChannel);
|
$$SnLocalChatChannelTableTableManager(_db, _db.snLocalChatChannel);
|
||||||
$$SnLocalChatMessageTableTableManager get snLocalChatMessage =>
|
$$SnLocalChatMessageTableTableManager get snLocalChatMessage =>
|
||||||
$$SnLocalChatMessageTableTableManager(_db, _db.snLocalChatMessage);
|
$$SnLocalChatMessageTableTableManager(_db, _db.snLocalChatMessage);
|
||||||
|
$$SnLocalKeyPairTableTableManager get snLocalKeyPair =>
|
||||||
|
$$SnLocalKeyPairTableTableManager(_db, _db.snLocalKeyPair);
|
||||||
}
|
}
|
||||||
|
163
lib/database/database.steps.dart
Normal file
163
lib/database/database.steps.dart
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// dart format width=80
|
||||||
|
import 'package:drift/internal/versioned_schema.dart' as i0;
|
||||||
|
import 'package:drift/drift.dart' as i1;
|
||||||
|
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
|
||||||
|
|
||||||
|
// GENERATED BY drift_dev, DO NOT MODIFY.
|
||||||
|
final class Schema2 extends i0.VersionedSchema {
|
||||||
|
Schema2({required super.database}) : super(version: 2);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
snLocalChatChannel,
|
||||||
|
snLocalChatMessage,
|
||||||
|
snLocalKeyPair,
|
||||||
|
];
|
||||||
|
late final Shape0 snLocalChatChannel = Shape0(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'sn_local_chat_channel',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_2,
|
||||||
|
_column_3,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape1 snLocalChatMessage = Shape1(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'sn_local_chat_message',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_4,
|
||||||
|
_column_2,
|
||||||
|
_column_3,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape2 snLocalKeyPair = Shape2(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'sn_local_key_pair',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_5,
|
||||||
|
_column_6,
|
||||||
|
_column_7,
|
||||||
|
_column_8,
|
||||||
|
_column_9,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape0 extends i0.VersionedTable {
|
||||||
|
Shape0({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get alias =>
|
||||||
|
columnsByName['alias']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get content =>
|
||||||
|
columnsByName['content']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_0(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('id', aliasedName, false,
|
||||||
|
hasAutoIncrement: true,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
defaultConstraints:
|
||||||
|
i1.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||||
|
i1.GeneratedColumn<String> _column_1(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('alias', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<String> _column_2(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('content', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<DateTime> _column_3(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>('created_at', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.dateTime,
|
||||||
|
defaultValue: const CustomExpression(
|
||||||
|
'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)'));
|
||||||
|
|
||||||
|
class Shape1 extends i0.VersionedTable {
|
||||||
|
Shape1({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get channelId =>
|
||||||
|
columnsByName['channel_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get content =>
|
||||||
|
columnsByName['content']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_4(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('channel_id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
|
||||||
|
class Shape2 extends i0.VersionedTable {
|
||||||
|
Shape2({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get accountId =>
|
||||||
|
columnsByName['account_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get publicKey =>
|
||||||
|
columnsByName['public_key']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get privateKey =>
|
||||||
|
columnsByName['private_key']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<bool> get isActive =>
|
||||||
|
columnsByName['is_active']! as i1.GeneratedColumn<bool>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_5(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<int> _column_6(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('account_id', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
i1.GeneratedColumn<String> _column_7(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('public_key', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<String> _column_8(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('private_key', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.string);
|
||||||
|
i1.GeneratedColumn<bool> _column_9(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<bool>('is_active', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.bool,
|
||||||
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("is_active" IN (0, 1))'),
|
||||||
|
defaultValue: const CustomExpression('0'));
|
||||||
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
|
}) {
|
||||||
|
return (currentVersion, database) async {
|
||||||
|
switch (currentVersion) {
|
||||||
|
case 1:
|
||||||
|
final schema = Schema2(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from1To2(migrator, schema);
|
||||||
|
return 2;
|
||||||
|
default:
|
||||||
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.OnUpgrade stepByStep({
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
|
}) =>
|
||||||
|
i0.VersionedSchema.stepByStepHelper(
|
||||||
|
step: migrationSteps(
|
||||||
|
from1To2: from1To2,
|
||||||
|
));
|
13
lib/database/keypair.dart
Normal file
13
lib/database/keypair.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
class SnLocalKeyPair extends Table {
|
||||||
|
TextColumn get id => text()();
|
||||||
|
|
||||||
|
IntColumn get accountId => integer()();
|
||||||
|
|
||||||
|
TextColumn get publicKey => text()();
|
||||||
|
|
||||||
|
TextColumn get privateKey => text().nullable()();
|
||||||
|
|
||||||
|
BoolColumn get isActive => boolean().withDefault(Constant(false))();
|
||||||
|
}
|
@ -25,6 +25,7 @@ import 'package:surface/providers/channel.dart';
|
|||||||
import 'package:surface/providers/chat_call.dart';
|
import 'package:surface/providers/chat_call.dart';
|
||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/database.dart';
|
import 'package:surface/providers/database.dart';
|
||||||
|
import 'package:surface/providers/keypair.dart';
|
||||||
import 'package:surface/providers/link_preview.dart';
|
import 'package:surface/providers/link_preview.dart';
|
||||||
import 'package:surface/providers/navigation.dart';
|
import 'package:surface/providers/navigation.dart';
|
||||||
import 'package:surface/providers/notification.dart';
|
import 'package:surface/providers/notification.dart';
|
||||||
@ -108,7 +109,8 @@ void main() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!kIsWeb && Platform.isAndroid) {
|
if (!kIsWeb && Platform.isAndroid) {
|
||||||
final ImagePickerPlatform imagePickerImplementation = ImagePickerPlatform.instance;
|
final ImagePickerPlatform imagePickerImplementation =
|
||||||
|
ImagePickerPlatform.instance;
|
||||||
if (imagePickerImplementation is ImagePickerAndroid) {
|
if (imagePickerImplementation is ImagePickerAndroid) {
|
||||||
imagePickerImplementation.useAndroidPhotoPicker = true;
|
imagePickerImplementation.useAndroidPhotoPicker = true;
|
||||||
}
|
}
|
||||||
@ -160,6 +162,7 @@ class SolianApp extends StatelessWidget {
|
|||||||
Provider(create: (ctx) => SnStickerProvider(ctx)),
|
Provider(create: (ctx) => SnStickerProvider(ctx)),
|
||||||
ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)),
|
ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)),
|
||||||
ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)),
|
ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)),
|
||||||
|
Provider(create: (ctx) => KeyPairProvider(ctx)),
|
||||||
ChangeNotifierProvider(create: (ctx) => NotificationProvider(ctx)),
|
ChangeNotifierProvider(create: (ctx) => NotificationProvider(ctx)),
|
||||||
ChangeNotifierProvider(create: (ctx) => ChatChannelProvider(ctx)),
|
ChangeNotifierProvider(create: (ctx) => ChatChannelProvider(ctx)),
|
||||||
ChangeNotifierProvider(create: (ctx) => ChatCallProvider(ctx)),
|
ChangeNotifierProvider(create: (ctx) => ChatCallProvider(ctx)),
|
||||||
@ -227,7 +230,8 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
if (prefs.containsKey('first_boot_time')) {
|
if (prefs.containsKey('first_boot_time')) {
|
||||||
final rawTime = prefs.getString('first_boot_time');
|
final rawTime = prefs.getString('first_boot_time');
|
||||||
final time = DateTime.tryParse(rawTime ?? '');
|
final time = DateTime.tryParse(rawTime ?? '');
|
||||||
if (time != null && time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
|
if (time != null &&
|
||||||
|
time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
|
||||||
final inAppReview = InAppReview.instance;
|
final inAppReview = InAppReview.instance;
|
||||||
if (prefs.getBool('rating_requested') == true) return;
|
if (prefs.getBool('rating_requested') == true) return;
|
||||||
if (await inAppReview.isAvailable()) {
|
if (await inAppReview.isAvailable()) {
|
||||||
@ -258,12 +262,18 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
final remoteVersionString = resp.data?['tag_name'] ?? '0.0.0+0';
|
final remoteVersionString = resp.data?['tag_name'] ?? '0.0.0+0';
|
||||||
final remoteVersion = Version.parse(remoteVersionString.split('+').first);
|
final remoteVersion = Version.parse(remoteVersionString.split('+').first);
|
||||||
final localVersion = Version.parse(localVersionString.split('+').first);
|
final localVersion = Version.parse(localVersionString.split('+').first);
|
||||||
final remoteBuildNumber = int.tryParse(remoteVersionString.split('+').last) ?? 0;
|
final remoteBuildNumber =
|
||||||
final localBuildNumber = int.tryParse(localVersionString.split('+').last) ?? 0;
|
int.tryParse(remoteVersionString.split('+').last) ?? 0;
|
||||||
logging.info("[Update] Local: $localVersionString, Remote: $remoteVersionString");
|
final localBuildNumber =
|
||||||
if ((remoteVersion > localVersion || remoteBuildNumber > localBuildNumber) && mounted) {
|
int.tryParse(localVersionString.split('+').last) ?? 0;
|
||||||
|
logging.info(
|
||||||
|
"[Update] Local: $localVersionString, Remote: $remoteVersionString");
|
||||||
|
if ((remoteVersion > localVersion ||
|
||||||
|
remoteBuildNumber > localBuildNumber) &&
|
||||||
|
mounted) {
|
||||||
final config = context.read<ConfigProvider>();
|
final config = context.read<ConfigProvider>();
|
||||||
config.setUpdate(remoteVersionString, resp.data?['body'] ?? 'No changelog');
|
config.setUpdate(
|
||||||
|
remoteVersionString, resp.data?['body'] ?? 'No changelog');
|
||||||
logging.info("[Update] Update available: $remoteVersionString");
|
logging.info("[Update] Update available: $remoteVersionString");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -298,6 +308,10 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
notify.listen();
|
notify.listen();
|
||||||
await notify.registerPushNotifications();
|
await notify.registerPushNotifications();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
final kp = context.read<KeyPairProvider>();
|
||||||
|
await kp.reloadActive();
|
||||||
|
kp.listen();
|
||||||
|
if (!mounted) return;
|
||||||
final sticker = context.read<SnStickerProvider>();
|
final sticker = context.read<SnStickerProvider>();
|
||||||
await sticker.listSticker();
|
await sticker.listSticker();
|
||||||
logging.info('[Bootstrap] Everything initialized!');
|
logging.info('[Bootstrap] Everything initialized!');
|
||||||
@ -355,7 +369,9 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
Future<void> _trayInitialization() async {
|
Future<void> _trayInitialization() async {
|
||||||
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
||||||
|
|
||||||
final icon = Platform.isWindows ? 'assets/icon/tray-icon.ico' : 'assets/icon/tray-icon.png';
|
final icon = Platform.isWindows
|
||||||
|
? 'assets/icon/tray-icon.ico'
|
||||||
|
: 'assets/icon/tray-icon.png';
|
||||||
final appVersion = await PackageInfo.fromPlatform();
|
final appVersion = await PackageInfo.fromPlatform();
|
||||||
|
|
||||||
trayManager.addListener(this);
|
trayManager.addListener(this);
|
||||||
|
237
lib/providers/keypair.dart
Normal file
237
lib/providers/keypair.dart
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/database/database.dart';
|
||||||
|
import 'package:surface/logger.dart';
|
||||||
|
import 'package:surface/providers/database.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/providers/websocket.dart';
|
||||||
|
import 'package:surface/types/keypair.dart';
|
||||||
|
import 'package:fast_rsa/fast_rsa.dart';
|
||||||
|
import 'package:surface/types/websocket.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
// Currently the keypair only provide RSA encryption
|
||||||
|
// Supported by the `fast_rsa` package
|
||||||
|
class KeyPairProvider {
|
||||||
|
late final DatabaseProvider _dt;
|
||||||
|
late final UserProvider _ua;
|
||||||
|
late final WebSocketProvider _ws;
|
||||||
|
|
||||||
|
SnKeyPair? activeKp;
|
||||||
|
|
||||||
|
KeyPairProvider(BuildContext context) {
|
||||||
|
_dt = context.read<DatabaseProvider>();
|
||||||
|
_ua = context.read<UserProvider>();
|
||||||
|
_ws = context.read<WebSocketProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void listen() {
|
||||||
|
_ws.pk.stream.listen((event) {
|
||||||
|
switch (event.method) {
|
||||||
|
case 'kex.ack':
|
||||||
|
ackKeyExchange(event);
|
||||||
|
break;
|
||||||
|
case 'key.ask':
|
||||||
|
replyAskKeyExchange(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> decryptText(String text, String kpId) async {
|
||||||
|
final kp = await (_dt.db.snLocalKeyPair.select()
|
||||||
|
..where((e) => e.id.equals(kpId)))
|
||||||
|
.getSingleOrNull();
|
||||||
|
if (kp == null) throw Exception('Key pair not found');
|
||||||
|
return await RSA.decryptPKCS1v15(text, kp.privateKey!);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> encryptText(String text) async {
|
||||||
|
if (activeKp == null) throw Exception('No active key pair');
|
||||||
|
return await RSA.encryptPKCS1v15(text, activeKp!.publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, Completer<SnKeyPair>> _requests = {};
|
||||||
|
|
||||||
|
Future<SnKeyPair> askKeyExchange(int kpOwner, String kpId) async {
|
||||||
|
if (_requests.containsKey(kpId)) return await _requests[kpId]!.future;
|
||||||
|
|
||||||
|
final completer = Completer<SnKeyPair>();
|
||||||
|
_requests[kpId] = completer;
|
||||||
|
|
||||||
|
_ws.conn?.sink.add(
|
||||||
|
jsonEncode(WebSocketPackage(
|
||||||
|
method: 'key.ask',
|
||||||
|
endpoint: 'id',
|
||||||
|
payload: {
|
||||||
|
'keypair_id': kpId,
|
||||||
|
'user_id': kpOwner,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Future.any([
|
||||||
|
_requests[kpId]!.future,
|
||||||
|
Future.delayed(const Duration(seconds: 60), () {
|
||||||
|
_requests.remove(kpId);
|
||||||
|
throw TimeoutException("Key exchange timed out");
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> ackKeyExchange(WebSocketPackage pkt) async {
|
||||||
|
if (pkt.payload == null) return;
|
||||||
|
final kpMeta = SnKeyPair(
|
||||||
|
id: pkt.payload!['keypair_id'] as String,
|
||||||
|
accountId: pkt.payload!['user_id'] as int,
|
||||||
|
publicKey: pkt.payload!['public_key'] as String,
|
||||||
|
privateKey: pkt.payload?['private_key'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (_requests.containsKey(kpMeta.id)) {
|
||||||
|
_requests[kpMeta.id]!.complete(kpMeta);
|
||||||
|
_requests.remove(kpMeta.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the keypair to the local database
|
||||||
|
await _dt.db.snLocalKeyPair.insertOne(
|
||||||
|
SnLocalKeyPairCompanion.insert(
|
||||||
|
id: kpMeta.id,
|
||||||
|
accountId: kpMeta.accountId,
|
||||||
|
publicKey: kpMeta.publicKey,
|
||||||
|
privateKey: Value(kpMeta.privateKey),
|
||||||
|
),
|
||||||
|
onConflict: DoUpdate(
|
||||||
|
(_) => SnLocalKeyPairCompanion.custom(
|
||||||
|
publicKey: Constant(kpMeta.publicKey),
|
||||||
|
privateKey: Constant(kpMeta.privateKey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> replyAskKeyExchange(WebSocketPackage pkt) async {
|
||||||
|
final kpId = pkt.payload!['keypair_id'] as String;
|
||||||
|
final userId = pkt.payload!['user_id'] as int;
|
||||||
|
final clientId = pkt.payload!['client_id'] as String;
|
||||||
|
|
||||||
|
final localKp = await (_dt.db.snLocalKeyPair.select()
|
||||||
|
..where((e) => e.id.equals(kpId))
|
||||||
|
..limit(1))
|
||||||
|
.getSingleOrNull();
|
||||||
|
if (localKp == null) return;
|
||||||
|
|
||||||
|
logging.info(
|
||||||
|
'[Kex] Reply to key exchange request of $kpId from user $userId',
|
||||||
|
);
|
||||||
|
|
||||||
|
// We do not give the private key to the client
|
||||||
|
_ws.conn?.sink.add(jsonEncode(
|
||||||
|
WebSocketPackage(
|
||||||
|
method: 'kex.ack',
|
||||||
|
endpoint: 'id',
|
||||||
|
payload: {
|
||||||
|
'keypair_id': localKp.id,
|
||||||
|
'user_id': localKp.accountId,
|
||||||
|
'public_key': localKp.publicKey,
|
||||||
|
'client_id': clientId,
|
||||||
|
},
|
||||||
|
).toJson(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SnKeyPair?> reloadActive({bool autoEnroll = true}) async {
|
||||||
|
final kp = await (_dt.db.snLocalKeyPair.select()
|
||||||
|
..where((e) => e.accountId.equals(_ua.user!.id))
|
||||||
|
..where((e) => e.privateKey.isNotNull())
|
||||||
|
..where((e) => e.isActive.equals(true))
|
||||||
|
..limit(1))
|
||||||
|
.getSingleOrNull();
|
||||||
|
|
||||||
|
if (kp != null) {
|
||||||
|
activeKp = SnKeyPair(
|
||||||
|
id: kp.id,
|
||||||
|
accountId: kp.accountId,
|
||||||
|
publicKey: kp.publicKey,
|
||||||
|
privateKey: kp.privateKey,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kp == null && autoEnroll) {
|
||||||
|
return await enrollNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeKp;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<SnKeyPair>> listKeyPair() async {
|
||||||
|
final kps = await (_dt.db.snLocalKeyPair.select()).get();
|
||||||
|
return kps
|
||||||
|
.map((e) => SnKeyPair(
|
||||||
|
id: e.id,
|
||||||
|
accountId: e.accountId,
|
||||||
|
publicKey: e.publicKey,
|
||||||
|
privateKey: e.privateKey,
|
||||||
|
isActive: e.isActive,
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> activeKeyPair(String kpId) async {
|
||||||
|
final kp = await (_dt.db.snLocalKeyPair.select()
|
||||||
|
..where((e) => e.id.equals(kpId))
|
||||||
|
..where((e) => e.privateKey.isNotNull())
|
||||||
|
..limit(1))
|
||||||
|
.getSingleOrNull();
|
||||||
|
if (kp == null) return;
|
||||||
|
|
||||||
|
await _dt.db.transaction(() async {
|
||||||
|
await (_dt.db.update(_dt.db.snLocalKeyPair)
|
||||||
|
..where((e) => e.isActive.equals(true)))
|
||||||
|
.write(SnLocalKeyPairCompanion(isActive: Value(false)));
|
||||||
|
|
||||||
|
await (_dt.db.update(_dt.db.snLocalKeyPair)
|
||||||
|
..where((e) => e.id.equals(kp.id)))
|
||||||
|
.write(SnLocalKeyPairCompanion(isActive: Value(true)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SnKeyPair> enrollNew() async {
|
||||||
|
if (!_ua.isAuthorized) throw Exception('Unauthorized');
|
||||||
|
|
||||||
|
final id = const Uuid().v4();
|
||||||
|
final kp = await RSA.generate(2048);
|
||||||
|
final kpMeta = SnKeyPair(
|
||||||
|
id: id,
|
||||||
|
accountId: _ua.user!.id,
|
||||||
|
publicKey: kp.publicKey,
|
||||||
|
privateKey: kp.privateKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save the keypair to the local database
|
||||||
|
// If there is already one with private key, it will be overwritten
|
||||||
|
await _dt.db.transaction(() async {
|
||||||
|
await (_dt.db.update(_dt.db.snLocalKeyPair)
|
||||||
|
..where((e) => e.isActive.equals(true)))
|
||||||
|
.write(SnLocalKeyPairCompanion(isActive: Value(false)));
|
||||||
|
|
||||||
|
await _dt.db.snLocalKeyPair.insertOne(
|
||||||
|
SnLocalKeyPairCompanion.insert(
|
||||||
|
id: kpMeta.id,
|
||||||
|
accountId: kpMeta.accountId,
|
||||||
|
publicKey: kpMeta.publicKey,
|
||||||
|
privateKey: Value(kpMeta.privateKey),
|
||||||
|
isActive: Value(true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await reloadActive(autoEnroll: false);
|
||||||
|
|
||||||
|
return kpMeta;
|
||||||
|
}
|
||||||
|
}
|
@ -117,7 +117,8 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
(event) {
|
(event) {
|
||||||
final packet = WebSocketPackage.fromJson(jsonDecode(event));
|
final packet = WebSocketPackage.fromJson(jsonDecode(event));
|
||||||
logging.debug(
|
logging.debug(
|
||||||
'[Websocket] Incoming message: ${packet.method} ${packet.message}');
|
'[Websocket] Incoming message: ${packet.method} ${packet.message}',
|
||||||
|
);
|
||||||
pk.sink.add(packet);
|
pk.sink.add(packet);
|
||||||
},
|
},
|
||||||
onDone: () {
|
onDone: () {
|
||||||
|
@ -6,6 +6,7 @@ import 'package:surface/screens/account.dart';
|
|||||||
import 'package:surface/screens/account/account_settings.dart';
|
import 'package:surface/screens/account/account_settings.dart';
|
||||||
import 'package:surface/screens/account/badges.dart';
|
import 'package:surface/screens/account/badges.dart';
|
||||||
import 'package:surface/screens/account/factor_settings.dart';
|
import 'package:surface/screens/account/factor_settings.dart';
|
||||||
|
import 'package:surface/screens/account/keypairs.dart';
|
||||||
import 'package:surface/screens/account/profile_page.dart';
|
import 'package:surface/screens/account/profile_page.dart';
|
||||||
import 'package:surface/screens/account/profile_edit.dart';
|
import 'package:surface/screens/account/profile_edit.dart';
|
||||||
import 'package:surface/screens/account/publishers/publisher_edit.dart';
|
import 'package:surface/screens/account/publishers/publisher_edit.dart';
|
||||||
@ -43,8 +44,8 @@ import 'package:surface/types/post.dart';
|
|||||||
import 'package:surface/widgets/about.dart';
|
import 'package:surface/widgets/about.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
Widget _fadeThroughTransition(
|
Widget _fadeThroughTransition(BuildContext context, Animation<double> animation,
|
||||||
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
Animation<double> secondaryAnimation, Widget child) {
|
||||||
return FadeThroughTransition(
|
return FadeThroughTransition(
|
||||||
animation: animation,
|
animation: animation,
|
||||||
secondaryAnimation: secondaryAnimation,
|
secondaryAnimation: secondaryAnimation,
|
||||||
@ -86,13 +87,15 @@ final _appRoutes = [
|
|||||||
name: 'postSearch',
|
name: 'postSearch',
|
||||||
builder: (context, state) => PostSearchScreen(
|
builder: (context, state) => PostSearchScreen(
|
||||||
initialTags: state.uri.queryParameters['tags']?.split(','),
|
initialTags: state.uri.queryParameters['tags']?.split(','),
|
||||||
initialCategories: state.uri.queryParameters['categories']?.split(','),
|
initialCategories:
|
||||||
|
state.uri.queryParameters['categories']?.split(','),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/publishers/:name',
|
path: '/publishers/:name',
|
||||||
name: 'postPublisher',
|
name: 'postPublisher',
|
||||||
builder: (context, state) => PostPublisherScreen(name: state.pathParameters['name']!),
|
builder: (context, state) =>
|
||||||
|
PostPublisherScreen(name: state.pathParameters['name']!),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/:slug',
|
path: '/:slug',
|
||||||
@ -119,6 +122,11 @@ final _appRoutes = [
|
|||||||
name: 'accountWallet',
|
name: 'accountWallet',
|
||||||
builder: (context, state) => const WalletScreen(),
|
builder: (context, state) => const WalletScreen(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/keypairs',
|
||||||
|
name: 'accountKeyPairs',
|
||||||
|
builder: (context, state) => const KeyPairScreen(),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'accountSettings',
|
name: 'accountSettings',
|
||||||
@ -222,7 +230,8 @@ final _appRoutes = [
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/:alias',
|
path: '/:alias',
|
||||||
name: 'realmDetail',
|
name: 'realmDetail',
|
||||||
builder: (context, state) => RealmDetailScreen(alias: state.pathParameters['alias']!),
|
builder: (context, state) =>
|
||||||
|
RealmDetailScreen(alias: state.pathParameters['alias']!),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -45,7 +45,8 @@ class AccountScreen extends StatelessWidget {
|
|||||||
? Stack(
|
? Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner), fit: BoxFit.cover),
|
AutoResizeUniversalImage(sn.getAttachmentUrl(ua.user!.banner),
|
||||||
|
fit: BoxFit.cover),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@ -79,7 +80,9 @@ class AccountScreen extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: ua.isAuthorized ? _AuthorizedAccountScreen() : _UnauthorizedAccountScreen(),
|
child: ua.isAuthorized
|
||||||
|
? _AuthorizedAccountScreen()
|
||||||
|
: _UnauthorizedAccountScreen(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -115,9 +118,11 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
textBaseline: TextBaseline.alphabetic,
|
textBaseline: TextBaseline.alphabetic,
|
||||||
children: [
|
children: [
|
||||||
Text(ua.user!.nick).textStyle(Theme.of(context).textTheme.titleLarge!),
|
Text(ua.user!.nick)
|
||||||
|
.textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text('@${ua.user!.name}').textStyle(Theme.of(context).textTheme.bodySmall!),
|
Text('@${ua.user!.name}')
|
||||||
|
.textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
@ -183,6 +188,16 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
|||||||
GoRouter.of(context).pushNamed('accountBadges');
|
GoRouter.of(context).pushNamed('accountBadges');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('accountKeyPairs').tr(),
|
||||||
|
subtitle: Text('accountKeyPairsDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.key),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('accountKeyPairs');
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('accountSettings').tr(),
|
title: Text('accountSettings').tr(),
|
||||||
subtitle: Text('accountSettingsSubtitle').tr(),
|
subtitle: Text('accountSettingsSubtitle').tr(),
|
||||||
@ -236,7 +251,9 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
|
|||||||
child: Icon(Symbols.waving_hand, size: 28),
|
child: Icon(Symbols.waving_hand, size: 28),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text('accountIntroTitle').tr().textStyle(Theme.of(context).textTheme.titleLarge!),
|
Text('accountIntroTitle')
|
||||||
|
.tr()
|
||||||
|
.textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||||
Text('accountIntroSubtitle').tr(),
|
Text('accountIntroSubtitle').tr(),
|
||||||
],
|
],
|
||||||
).padding(all: 20),
|
).padding(all: 20),
|
||||||
|
106
lib/screens/account/keypairs.dart
Normal file
106
lib/screens/account/keypairs.dart
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/keypair.dart';
|
||||||
|
import 'package:surface/types/keypair.dart';
|
||||||
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
|
class KeyPairScreen extends StatefulWidget {
|
||||||
|
const KeyPairScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<KeyPairScreen> createState() => _KeyPairScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _KeyPairScreenState extends State<KeyPairScreen> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
List<SnKeyPair>? _keyPairs;
|
||||||
|
|
||||||
|
Future<void> _loadKeyPairs() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final kps = await context.read<KeyPairProvider>().listKeyPair();
|
||||||
|
setState(() {
|
||||||
|
_keyPairs = kps;
|
||||||
|
_isBusy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadKeyPairs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('screenKeyPairs').tr(),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
LoadingIndicator(isActive: _isBusy),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
title: Text('enrollNewKeyPair').tr(),
|
||||||
|
subtitle: Text('enrollNewKeyPairDescription').tr(),
|
||||||
|
onTap: () async {
|
||||||
|
await context.read<KeyPairProvider>().enrollNew();
|
||||||
|
_loadKeyPairs();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
if (_keyPairs != null)
|
||||||
|
Expanded(
|
||||||
|
child: MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: _loadKeyPairs,
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: _keyPairs!.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final kp = _keyPairs![index];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(kp.id.toUpperCase()),
|
||||||
|
subtitle: Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
if (kp.privateKey != null)
|
||||||
|
Text(
|
||||||
|
'keyPairHasPrivateKey'.tr(),
|
||||||
|
),
|
||||||
|
if (kp.privateKey != null) Text('·'),
|
||||||
|
Flexible(
|
||||||
|
flex: 1,
|
||||||
|
child: Text(
|
||||||
|
'UID #${kp.accountId.toString().padLeft(8, '0')}',
|
||||||
|
style: GoogleFonts.robotoMono(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(Symbols.check),
|
||||||
|
onPressed: kp.isActive == true
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
final k = context.read<KeyPairProvider>();
|
||||||
|
await k.activeKeyPair(kp.id);
|
||||||
|
_loadKeyPairs();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -68,38 +68,35 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
_banner = prof.banner;
|
_banner = prof.banner;
|
||||||
_links = prof.profile!.links.entries.map((ele) => (ele.key, ele.value)).toList();
|
_links = prof.profile!.links.entries.map((ele) => (ele.key, ele.value)).toList();
|
||||||
_birthday = prof.profile!.birthday?.toLocal();
|
_birthday = prof.profile!.birthday?.toLocal();
|
||||||
if(_birthday != null) {
|
if (_birthday != null) {
|
||||||
_birthdayController.text = DateFormat(_kDateFormat).format(
|
_birthdayController.text = DateFormat(_kDateFormat).format(prof.profile!.birthday!.toLocal());
|
||||||
prof.profile!.birthday!.toLocal(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _selectBirthday() async {
|
void _selectBirthday() async {
|
||||||
await showCupertinoModalPopup<DateTime?>(
|
await showCupertinoModalPopup<DateTime?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) => Container(
|
builder:
|
||||||
height: 216,
|
(BuildContext context) => Container(
|
||||||
padding: const EdgeInsets.only(top: 6.0),
|
height: 216,
|
||||||
margin: EdgeInsets.only(
|
padding: const EdgeInsets.only(top: 6.0),
|
||||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
margin: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||||
),
|
color: Theme.of(context).colorScheme.surface,
|
||||||
color: Theme.of(context).colorScheme.surface,
|
child: SafeArea(
|
||||||
child: SafeArea(
|
top: false,
|
||||||
top: false,
|
child: CupertinoDatePicker(
|
||||||
child: CupertinoDatePicker(
|
initialDateTime: _birthday?.toLocal(),
|
||||||
initialDateTime: _birthday?.toLocal(),
|
mode: CupertinoDatePickerMode.date,
|
||||||
mode: CupertinoDatePickerMode.date,
|
use24hFormat: true,
|
||||||
use24hFormat: true,
|
onDateTimeChanged: (DateTime newDate) {
|
||||||
onDateTimeChanged: (DateTime newDate) {
|
setState(() {
|
||||||
setState(() {
|
_birthday = newDate;
|
||||||
_birthday = newDate;
|
_birthdayController.text = DateFormat(_kDateFormat).format(_birthday!);
|
||||||
_birthdayController.text = DateFormat(_kDateFormat).format(_birthday!);
|
});
|
||||||
});
|
},
|
||||||
},
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,32 +105,42 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
if (image == null) return;
|
if (image == null) return;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
final ImageProvider imageProvider = kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path));
|
final skipCrop = image.path.endsWith('.gif');
|
||||||
final aspectRatios =
|
|
||||||
place == 'banner' ? [CropAspectRatio(width: 16, height: 7)] : [CropAspectRatio(width: 1, height: 1)];
|
|
||||||
final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS))
|
|
||||||
? await showCupertinoImageCropper(
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
context,
|
|
||||||
allowedAspectRatios: aspectRatios,
|
|
||||||
imageProvider: imageProvider,
|
|
||||||
)
|
|
||||||
: await showMaterialImageCropper(
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
context,
|
|
||||||
allowedAspectRatios: aspectRatios,
|
|
||||||
imageProvider: imageProvider,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result == null) return;
|
Uint8List? rawBytes;
|
||||||
|
if (!skipCrop) {
|
||||||
|
final ImageProvider imageProvider = kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path));
|
||||||
|
final aspectRatios =
|
||||||
|
place == 'banner' ? [CropAspectRatio(width: 16, height: 7)] : [CropAspectRatio(width: 1, height: 1)];
|
||||||
|
final result =
|
||||||
|
(!kIsWeb && (Platform.isIOS || Platform.isMacOS))
|
||||||
|
? await showCupertinoImageCropper(
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
context,
|
||||||
|
allowedAspectRatios: aspectRatios,
|
||||||
|
imageProvider: imageProvider,
|
||||||
|
)
|
||||||
|
: await showMaterialImageCropper(
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
context,
|
||||||
|
allowedAspectRatios: aspectRatios,
|
||||||
|
imageProvider: imageProvider,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result == null) return;
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
rawBytes = (await result.uiImage.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List();
|
||||||
|
} else {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
rawBytes = await image.readAsBytes();
|
||||||
|
}
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final attach = context.read<SnAttachmentProvider>();
|
final attach = context.read<SnAttachmentProvider>();
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
|
||||||
|
|
||||||
final rawBytes = (await result.uiImage.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final attachment = await attach.directUploadOne(
|
final attachment = await attach.directUploadOne(
|
||||||
rawBytes,
|
rawBytes,
|
||||||
@ -145,10 +152,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
await sn.client.put(
|
await sn.client.put('/cgi/id/users/me/$place', data: {'attachment': attachment.rid});
|
||||||
'/cgi/id/users/me/$place',
|
|
||||||
data: {'attachment': attachment.rid},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
@ -184,7 +188,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
'location': _locationController.value.text,
|
'location': _locationController.value.text,
|
||||||
'birthday': _birthday?.toUtc().toIso8601String(),
|
'birthday': _birthday?.toUtc().toIso8601String(),
|
||||||
'links': {
|
'links': {
|
||||||
for (final link in _links!.where((ele) => ele.$1.isNotEmpty && ele.$2.isNotEmpty)) link.$1: link.$2
|
for (final link in _links!.where((ele) => ele.$1.isNotEmpty && ele.$2.isNotEmpty)) link.$1: link.$2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -231,10 +235,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(leading: const PageBackButton(), title: Text('screenAccountProfileEdit').tr()),
|
||||||
leading: const PageBackButton(),
|
|
||||||
title: Text('screenAccountProfileEdit').tr(),
|
|
||||||
),
|
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -253,12 +254,10 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
child: _banner != null
|
child:
|
||||||
? AutoResizeUniversalImage(
|
_banner != null
|
||||||
sn.getAttachmentUrl(_banner!),
|
? AutoResizeUniversalImage(sn.getAttachmentUrl(_banner!), fit: BoxFit.cover)
|
||||||
fit: BoxFit.cover,
|
: const SizedBox.shrink(),
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -299,10 +298,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
),
|
),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _nicknameController,
|
controller: _nicknameController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(border: const UnderlineInputBorder(), labelText: 'fieldNickname'.tr()),
|
||||||
border: const UnderlineInputBorder(),
|
|
||||||
labelText: 'fieldNickname'.tr(),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
@ -364,10 +360,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
minLines: 3,
|
minLines: 3,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(border: const UnderlineInputBorder(), labelText: 'fieldDescription'.tr()),
|
||||||
border: const UnderlineInputBorder(),
|
|
||||||
labelText: 'fieldDescription'.tr(),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
@ -384,42 +377,40 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
StyledWidget(IconButton(
|
StyledWidget(
|
||||||
icon: const Icon(Symbols.calendar_month),
|
IconButton(
|
||||||
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
icon: const Icon(Symbols.calendar_month),
|
||||||
padding: EdgeInsets.zero,
|
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||||
constraints: const BoxConstraints(),
|
padding: EdgeInsets.zero,
|
||||||
onPressed: () async {
|
constraints: const BoxConstraints(),
|
||||||
_timezoneController.text = await FlutterTimezone.getLocalTimezone();
|
onPressed: () async {
|
||||||
},
|
_timezoneController.text = await FlutterTimezone.getLocalTimezone();
|
||||||
)).padding(top: 6),
|
},
|
||||||
|
),
|
||||||
|
).padding(top: 6),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
StyledWidget(IconButton(
|
StyledWidget(
|
||||||
icon: const Icon(Symbols.clear),
|
IconButton(
|
||||||
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
icon: const Icon(Symbols.clear),
|
||||||
padding: EdgeInsets.zero,
|
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||||
constraints: const BoxConstraints(),
|
padding: EdgeInsets.zero,
|
||||||
onPressed: () {
|
constraints: const BoxConstraints(),
|
||||||
_timezoneController.clear();
|
onPressed: () {
|
||||||
},
|
_timezoneController.clear();
|
||||||
)).padding(top: 6),
|
},
|
||||||
|
),
|
||||||
|
).padding(top: 6),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _locationController,
|
controller: _locationController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(border: const UnderlineInputBorder(), labelText: 'fieldLocation'.tr()),
|
||||||
border: const UnderlineInputBorder(),
|
|
||||||
labelText: 'fieldLocation'.tr(),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _birthdayController,
|
controller: _birthdayController,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(border: const UnderlineInputBorder(), labelText: 'fieldBirthday'.tr()),
|
||||||
border: const UnderlineInputBorder(),
|
|
||||||
labelText: 'fieldBirthday'.tr(),
|
|
||||||
),
|
|
||||||
onTap: () => _selectBirthday(),
|
onTap: () => _selectBirthday(),
|
||||||
),
|
),
|
||||||
if (_links != null)
|
if (_links != null)
|
||||||
|
@ -68,16 +68,19 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
|
|||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sn.client.put('/cgi/co/publishers/${widget.name}', data: {
|
await sn.client.put(
|
||||||
'avatar': _avatar,
|
'/cgi/co/publishers/${widget.name}',
|
||||||
'banner': _banner,
|
data: {
|
||||||
'nick': _nickController.text,
|
'avatar': _avatar,
|
||||||
'name': _nameController.text,
|
'banner': _banner,
|
||||||
'description': _descriptionController.text,
|
'nick': _nickController.text,
|
||||||
});
|
'name': _nameController.text,
|
||||||
|
'description': _descriptionController.text,
|
||||||
|
},
|
||||||
|
);
|
||||||
if (mounted) Navigator.pop(context, true);
|
if (mounted) Navigator.pop(context, true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if(mounted) context.showErrorDialog(err);
|
if (mounted) context.showErrorDialog(err);
|
||||||
} finally {
|
} finally {
|
||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
@ -108,32 +111,42 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
|
|||||||
if (image == null) return;
|
if (image == null) return;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
final ImageProvider imageProvider = kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path));
|
final skipCrop = image.path.endsWith('.gif');
|
||||||
final aspectRatios =
|
|
||||||
place == 'banner' ? [CropAspectRatio(width: 16, height: 7)] : [CropAspectRatio(width: 1, height: 1)];
|
|
||||||
final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS))
|
|
||||||
? await showCupertinoImageCropper(
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
context,
|
|
||||||
allowedAspectRatios: aspectRatios,
|
|
||||||
imageProvider: imageProvider,
|
|
||||||
)
|
|
||||||
: await showMaterialImageCropper(
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
context,
|
|
||||||
allowedAspectRatios: aspectRatios,
|
|
||||||
imageProvider: imageProvider,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result == null) return;
|
Uint8List? rawBytes;
|
||||||
|
if (!skipCrop) {
|
||||||
|
final ImageProvider imageProvider = kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path));
|
||||||
|
final aspectRatios =
|
||||||
|
place == 'banner' ? [CropAspectRatio(width: 16, height: 7)] : [CropAspectRatio(width: 1, height: 1)];
|
||||||
|
final result =
|
||||||
|
(!kIsWeb && (Platform.isIOS || Platform.isMacOS))
|
||||||
|
? await showCupertinoImageCropper(
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
context,
|
||||||
|
allowedAspectRatios: aspectRatios,
|
||||||
|
imageProvider: imageProvider,
|
||||||
|
)
|
||||||
|
: await showMaterialImageCropper(
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
context,
|
||||||
|
allowedAspectRatios: aspectRatios,
|
||||||
|
imageProvider: imageProvider,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result == null) return;
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
rawBytes = (await result.uiImage.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List();
|
||||||
|
} else {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
rawBytes = await image.readAsBytes();
|
||||||
|
}
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final attach = context.read<SnAttachmentProvider>();
|
final attach = context.read<SnAttachmentProvider>();
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
|
||||||
|
|
||||||
final rawBytes = (await result.uiImage.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final attachment = await attach.directUploadOne(
|
final attachment = await attach.directUploadOne(
|
||||||
rawBytes,
|
rawBytes,
|
||||||
@ -178,10 +191,7 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
|
|||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(leading: PageBackButton(), title: Text('screenAccountPublisherEdit').tr()),
|
||||||
leading: PageBackButton(),
|
|
||||||
title: Text('screenAccountPublisherEdit').tr(),
|
|
||||||
),
|
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@ -199,12 +209,10 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
|
|||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
child: _banner != null
|
child:
|
||||||
? AutoResizeUniversalImage(
|
_banner != null
|
||||||
sn.getAttachmentUrl(_banner!),
|
? AutoResizeUniversalImage(sn.getAttachmentUrl(_banner!), fit: BoxFit.cover)
|
||||||
fit: BoxFit.cover,
|
: const SizedBox.shrink(),
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -242,9 +250,7 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
|
|||||||
const Gap(4),
|
const Gap(4),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _nickController,
|
controller: _nickController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(labelText: 'fieldNickname'.tr()),
|
||||||
labelText: 'fieldNickname'.tr(),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
@ -252,9 +258,7 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
|
|||||||
controller: _descriptionController,
|
controller: _descriptionController,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
minLines: 3,
|
minLines: 3,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(labelText: 'fieldDescription'.tr()),
|
||||||
labelText: 'fieldDescription'.tr(),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
@ -275,7 +279,7 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
|
|||||||
icon: const Icon(Symbols.save),
|
icon: const Icon(Symbols.save),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 24, vertical: 12),
|
).padding(horizontal: 24, vertical: 12),
|
||||||
),
|
),
|
||||||
|
@ -58,18 +58,12 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final nty = context.read<NotificationProvider>();
|
final nty = context.read<NotificationProvider>();
|
||||||
final resp =
|
final resp = await sn.client.get(
|
||||||
await sn.client.get('/cgi/id/notifications', queryParameters: {
|
'/cgi/id/notifications',
|
||||||
'take': 10,
|
queryParameters: {'take': 10, 'offset': _notifications.length},
|
||||||
'offset': _notifications.length,
|
|
||||||
});
|
|
||||||
_totalCount = resp.data['count'];
|
|
||||||
_notifications.addAll(
|
|
||||||
resp.data['data']
|
|
||||||
?.map((e) => SnNotification.fromJson(e))
|
|
||||||
.cast<SnNotification>() ??
|
|
||||||
[],
|
|
||||||
);
|
);
|
||||||
|
_totalCount = resp.data['count'];
|
||||||
|
_notifications.addAll(resp.data['data']?.map((e) => SnNotification.fromJson(e)).cast<SnNotification>() ?? []);
|
||||||
nty.updateTray();
|
nty.updateTray();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@ -104,9 +98,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
nty.clear();
|
nty.clear();
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showSnackbar(
|
context.showSnackbar('notificationMarkAllReadPrompt'.plural(resp.data['count']));
|
||||||
'notificationMarkAllReadPrompt'.plural(resp.data['count']),
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -130,9 +122,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
_fetchNotifications();
|
_fetchNotifications();
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showSnackbar(
|
context.showSnackbar('notificationMarkOneReadPrompt'.tr(args: ['#${notification.id}']));
|
||||||
'notificationMarkOneReadPrompt'.tr(args: ['#${notification.id}']),
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -153,13 +143,8 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
|
|
||||||
if (!ua.isAuthorized) {
|
if (!ua.isAuthorized) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(leading: AutoAppBarLeading(), title: Text('screenNotification').tr()),
|
||||||
leading: AutoAppBarLeading(),
|
body: Center(child: UnauthorizedHint()),
|
||||||
title: Text('screenNotification').tr(),
|
|
||||||
),
|
|
||||||
body: Center(
|
|
||||||
child: UnauthorizedHint(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,10 +153,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenNotification').tr(),
|
title: Text('screenNotification').tr(),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(icon: const Icon(Symbols.checklist), onPressed: _isSubmitting ? null : _markAllAsRead),
|
||||||
icon: const Icon(Symbols.checklist),
|
|
||||||
onPressed: _isSubmitting ? null : _markAllAsRead,
|
|
||||||
),
|
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -185,17 +167,13 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
return _fetchNotifications();
|
return _fetchNotifications();
|
||||||
},
|
},
|
||||||
child: InfiniteList(
|
child: InfiniteList(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(top: 16, bottom: math.max(MediaQuery.of(context).padding.bottom, 16)),
|
||||||
top: 16,
|
|
||||||
bottom: math.max(MediaQuery.of(context).padding.bottom, 16),
|
|
||||||
),
|
|
||||||
itemCount: _notifications.length,
|
itemCount: _notifications.length,
|
||||||
onFetchData: () {
|
onFetchData: () {
|
||||||
_fetchNotifications();
|
_fetchNotifications();
|
||||||
},
|
},
|
||||||
isLoading: _isBusy,
|
isLoading: _isBusy,
|
||||||
hasReachedMax: _totalCount != null &&
|
hasReachedMax: _totalCount != null && _notifications.length >= _totalCount!,
|
||||||
_notifications.length >= _totalCount!,
|
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
final nty = _notifications[idx];
|
final nty = _notifications[idx];
|
||||||
return Row(
|
return Row(
|
||||||
@ -208,45 +186,26 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (nty.readAt == null)
|
if (nty.readAt == null)
|
||||||
StyledWidget(Badge(
|
StyledWidget(Badge(label: Text('notificationUnread').tr())).padding(bottom: 4),
|
||||||
label: Text('notificationUnread').tr(),
|
Text(nty.title, style: Theme.of(context).textTheme.titleMedium),
|
||||||
)).padding(bottom: 4),
|
|
||||||
Text(
|
|
||||||
nty.title,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
if (nty.subtitle != null)
|
if (nty.subtitle != null)
|
||||||
Text(
|
Text(nty.subtitle!, style: Theme.of(context).textTheme.titleSmall),
|
||||||
nty.subtitle!,
|
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
|
||||||
),
|
|
||||||
if (nty.subtitle != null) const Gap(4),
|
if (nty.subtitle != null) const Gap(4),
|
||||||
SelectionArea(
|
SelectionArea(child: MarkdownTextContent(content: nty.body, isAutoWarp: true)),
|
||||||
child: MarkdownTextContent(
|
|
||||||
content: nty.body,
|
|
||||||
isAutoWarp: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if ([
|
if ([
|
||||||
'interactive.reply',
|
'interactive.reply',
|
||||||
'interactive.feedback',
|
'interactive.feedback',
|
||||||
'interactive.subscription'
|
'interactive.subscription',
|
||||||
].contains(nty.topic) &&
|
].contains(nty.topic) &&
|
||||||
nty.metadata['related_post'] != null)
|
nty.metadata['related_post'] != null)
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
Radius.circular(8)),
|
border: Border.all(color: Theme.of(context).dividerColor, width: 1),
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: PostItem(
|
child: PostItem(
|
||||||
data: SnPost.fromJson(
|
data: SnPost.fromJson(nty.metadata['related_post']!),
|
||||||
nty.metadata['related_post']!,
|
|
||||||
),
|
|
||||||
showComments: false,
|
showComments: false,
|
||||||
showReactions: false,
|
showReactions: false,
|
||||||
showMenu: false,
|
showMenu: false,
|
||||||
@ -255,29 +214,18 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'postDetail',
|
'postDetail',
|
||||||
pathParameters: {
|
pathParameters: {'slug': nty.metadata['related_post']!['id'].toString()},
|
||||||
'slug': nty
|
|
||||||
.metadata['related_post']!['id']
|
|
||||||
.toString(),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).padding(top: 8),
|
).padding(top: 8),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(DateFormat('yy/MM/dd').format(nty.createdAt)).fontSize(12),
|
||||||
DateFormat('yy/MM/dd').format(nty.createdAt),
|
|
||||||
).fontSize(12),
|
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text('·', style: TextStyle(fontSize: 12)),
|
||||||
'·',
|
|
||||||
style: TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text(RelativeTime(context).format(nty.createdAt)).fontSize(12),
|
||||||
RelativeTime(context).format(nty.createdAt),
|
|
||||||
).fontSize(12),
|
|
||||||
],
|
],
|
||||||
).opacity(0.75),
|
).opacity(0.75),
|
||||||
],
|
],
|
||||||
@ -287,10 +235,8 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.check),
|
icon: const Icon(Symbols.check),
|
||||||
padding: EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
visualDensity:
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
const VisualDensity(horizontal: -4, vertical: -4),
|
onPressed: _isSubmitting ? null : () => _markOneAsRead(nty),
|
||||||
onPressed:
|
|
||||||
_isSubmitting ? null : () => _markOneAsRead(nty),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16);
|
).padding(horizontal: 16);
|
||||||
|
18
lib/types/keypair.dart
Normal file
18
lib/types/keypair.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'keypair.freezed.dart';
|
||||||
|
part 'keypair.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SnKeyPair with _$SnKeyPair {
|
||||||
|
const factory SnKeyPair({
|
||||||
|
required String id,
|
||||||
|
required int accountId,
|
||||||
|
required String publicKey,
|
||||||
|
bool? isActive,
|
||||||
|
String? privateKey,
|
||||||
|
}) = _SnKeyPair;
|
||||||
|
|
||||||
|
factory SnKeyPair.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$SnKeyPairFromJson(json);
|
||||||
|
}
|
241
lib/types/keypair.freezed.dart
Normal file
241
lib/types/keypair.freezed.dart
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'keypair.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnKeyPair {
|
||||||
|
String get id;
|
||||||
|
int get accountId;
|
||||||
|
String get publicKey;
|
||||||
|
bool? get isActive;
|
||||||
|
String? get privateKey;
|
||||||
|
|
||||||
|
/// Create a copy of SnKeyPair
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnKeyPairCopyWith<SnKeyPair> get copyWith =>
|
||||||
|
_$SnKeyPairCopyWithImpl<SnKeyPair>(this as SnKeyPair, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnKeyPair to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is SnKeyPair &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.accountId, accountId) ||
|
||||||
|
other.accountId == accountId) &&
|
||||||
|
(identical(other.publicKey, publicKey) ||
|
||||||
|
other.publicKey == publicKey) &&
|
||||||
|
(identical(other.isActive, isActive) ||
|
||||||
|
other.isActive == isActive) &&
|
||||||
|
(identical(other.privateKey, privateKey) ||
|
||||||
|
other.privateKey == privateKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
Object.hash(runtimeType, id, accountId, publicKey, isActive, privateKey);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnKeyPair(id: $id, accountId: $accountId, publicKey: $publicKey, isActive: $isActive, privateKey: $privateKey)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnKeyPairCopyWith<$Res> {
|
||||||
|
factory $SnKeyPairCopyWith(SnKeyPair value, $Res Function(SnKeyPair) _then) =
|
||||||
|
_$SnKeyPairCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{String id,
|
||||||
|
int accountId,
|
||||||
|
String publicKey,
|
||||||
|
bool? isActive,
|
||||||
|
String? privateKey});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnKeyPairCopyWithImpl<$Res> implements $SnKeyPairCopyWith<$Res> {
|
||||||
|
_$SnKeyPairCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnKeyPair _self;
|
||||||
|
final $Res Function(SnKeyPair) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnKeyPair
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? accountId = null,
|
||||||
|
Object? publicKey = null,
|
||||||
|
Object? isActive = freezed,
|
||||||
|
Object? privateKey = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _self.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
accountId: null == accountId
|
||||||
|
? _self.accountId
|
||||||
|
: accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
publicKey: null == publicKey
|
||||||
|
? _self.publicKey
|
||||||
|
: publicKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
isActive: freezed == isActive
|
||||||
|
? _self.isActive
|
||||||
|
: isActive // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool?,
|
||||||
|
privateKey: freezed == privateKey
|
||||||
|
? _self.privateKey
|
||||||
|
: privateKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _SnKeyPair implements SnKeyPair {
|
||||||
|
const _SnKeyPair(
|
||||||
|
{required this.id,
|
||||||
|
required this.accountId,
|
||||||
|
required this.publicKey,
|
||||||
|
this.isActive,
|
||||||
|
this.privateKey});
|
||||||
|
factory _SnKeyPair.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnKeyPairFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String id;
|
||||||
|
@override
|
||||||
|
final int accountId;
|
||||||
|
@override
|
||||||
|
final String publicKey;
|
||||||
|
@override
|
||||||
|
final bool? isActive;
|
||||||
|
@override
|
||||||
|
final String? privateKey;
|
||||||
|
|
||||||
|
/// Create a copy of SnKeyPair
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnKeyPairCopyWith<_SnKeyPair> get copyWith =>
|
||||||
|
__$SnKeyPairCopyWithImpl<_SnKeyPair>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnKeyPairToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _SnKeyPair &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.accountId, accountId) ||
|
||||||
|
other.accountId == accountId) &&
|
||||||
|
(identical(other.publicKey, publicKey) ||
|
||||||
|
other.publicKey == publicKey) &&
|
||||||
|
(identical(other.isActive, isActive) ||
|
||||||
|
other.isActive == isActive) &&
|
||||||
|
(identical(other.privateKey, privateKey) ||
|
||||||
|
other.privateKey == privateKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
Object.hash(runtimeType, id, accountId, publicKey, isActive, privateKey);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnKeyPair(id: $id, accountId: $accountId, publicKey: $publicKey, isActive: $isActive, privateKey: $privateKey)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnKeyPairCopyWith<$Res>
|
||||||
|
implements $SnKeyPairCopyWith<$Res> {
|
||||||
|
factory _$SnKeyPairCopyWith(
|
||||||
|
_SnKeyPair value, $Res Function(_SnKeyPair) _then) =
|
||||||
|
__$SnKeyPairCopyWithImpl;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{String id,
|
||||||
|
int accountId,
|
||||||
|
String publicKey,
|
||||||
|
bool? isActive,
|
||||||
|
String? privateKey});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnKeyPairCopyWithImpl<$Res> implements _$SnKeyPairCopyWith<$Res> {
|
||||||
|
__$SnKeyPairCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnKeyPair _self;
|
||||||
|
final $Res Function(_SnKeyPair) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnKeyPair
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? accountId = null,
|
||||||
|
Object? publicKey = null,
|
||||||
|
Object? isActive = freezed,
|
||||||
|
Object? privateKey = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_SnKeyPair(
|
||||||
|
id: null == id
|
||||||
|
? _self.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
accountId: null == accountId
|
||||||
|
? _self.accountId
|
||||||
|
: accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
publicKey: null == publicKey
|
||||||
|
? _self.publicKey
|
||||||
|
: publicKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
isActive: freezed == isActive
|
||||||
|
? _self.isActive
|
||||||
|
: isActive // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool?,
|
||||||
|
privateKey: freezed == privateKey
|
||||||
|
? _self.privateKey
|
||||||
|
: privateKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
24
lib/types/keypair.g.dart
Normal file
24
lib/types/keypair.g.dart
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'keypair.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_SnKeyPair _$SnKeyPairFromJson(Map<String, dynamic> json) => _SnKeyPair(
|
||||||
|
id: json['id'] as String,
|
||||||
|
accountId: (json['account_id'] as num).toInt(),
|
||||||
|
publicKey: json['public_key'] as String,
|
||||||
|
isActive: json['is_active'] as bool?,
|
||||||
|
privateKey: json['private_key'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnKeyPairToJson(_SnKeyPair instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'public_key': instance.publicKey,
|
||||||
|
'is_active': instance.isActive,
|
||||||
|
'private_key': instance.privateKey,
|
||||||
|
};
|
@ -16,6 +16,7 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
|
||||||
class AttachmentItem extends StatelessWidget {
|
class AttachmentItem extends StatelessWidget {
|
||||||
final SnAttachment? data;
|
final SnAttachment? data;
|
||||||
@ -289,6 +290,7 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
|
|||||||
shadows: labelShadows,
|
shadows: labelShadows,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
|
maxLines: 1,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -480,12 +482,13 @@ class _AttachmentItemContentAudioState extends State<_AttachmentItemContentAudio
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
widget.data.size.toString(),
|
widget.data.size.formatBytes(),
|
||||||
style: GoogleFonts.robotoMono(
|
style: GoogleFonts.robotoMono(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
shadows: labelShadows,
|
shadows: labelShadows,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
|
maxLines: 1,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -42,10 +42,7 @@ class AttachmentList extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AttachmentListState extends State<AttachmentList> {
|
class _AttachmentListState extends State<AttachmentList> {
|
||||||
late final List<String> heroTags = List.generate(
|
late final List<String> heroTags = List.generate(widget.data.length, (_) => const Uuid().v4());
|
||||||
widget.data.length,
|
|
||||||
(_) => const Uuid().v4(),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -61,13 +58,13 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
|
|
||||||
if (widget.data.isEmpty) return const SizedBox.shrink();
|
if (widget.data.isEmpty) return const SizedBox.shrink();
|
||||||
if (widget.data.length == 1) {
|
if (widget.data.length == 1) {
|
||||||
final singleAspectRatio = widget.data[0]?.data['ratio']?.toDouble() ??
|
final singleAspectRatio =
|
||||||
|
widget.data[0]?.data['ratio']?.toDouble() ??
|
||||||
switch (widget.data[0]?.mimetype.split('/').firstOrNull) {
|
switch (widget.data[0]?.mimetype.split('/').firstOrNull) {
|
||||||
'audio' => 16 / 9,
|
'audio' => 16 / 9,
|
||||||
'video' => 16 / 9,
|
'video' => 16 / 9,
|
||||||
_ => 1,
|
_ => 1,
|
||||||
}
|
}.toDouble();
|
||||||
.toDouble();
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: widget.padding ?? EdgeInsets.zero,
|
padding: widget.padding ?? EdgeInsets.zero,
|
||||||
@ -83,11 +80,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: AttachmentList.kDefaultRadius,
|
borderRadius: AttachmentList.kDefaultRadius,
|
||||||
child: AttachmentItem(
|
child: AttachmentItem(data: widget.data[0], heroTag: heroTags[0], fit: widget.fit),
|
||||||
data: widget.data[0],
|
|
||||||
heroTag: heroTags[0],
|
|
||||||
fit: widget.fit,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -115,10 +108,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
margin: widget.padding ?? EdgeInsets.zero,
|
margin: widget.padding ?? EdgeInsets.zero,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: backgroundColor,
|
color: backgroundColor,
|
||||||
border: Border(
|
border: Border(top: borderSide, bottom: borderSide),
|
||||||
top: borderSide,
|
|
||||||
bottom: borderSide,
|
|
||||||
),
|
|
||||||
borderRadius: AttachmentList.kDefaultRadius,
|
borderRadius: AttachmentList.kDefaultRadius,
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
@ -127,32 +117,29 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
crossAxisCount: math.min(widget.data.length, 2),
|
crossAxisCount: math.min(widget.data.length, 2),
|
||||||
crossAxisSpacing: 4,
|
crossAxisSpacing: 4,
|
||||||
mainAxisSpacing: 4,
|
mainAxisSpacing: 4,
|
||||||
children: widget.data
|
children:
|
||||||
.mapIndexed(
|
widget.data
|
||||||
(idx, ele) => GestureDetector(
|
.mapIndexed(
|
||||||
child: Container(
|
(idx, ele) => GestureDetector(
|
||||||
constraints: constraints,
|
child: Container(
|
||||||
child: AttachmentItem(
|
constraints: constraints,
|
||||||
data: ele,
|
child: AttachmentItem(data: ele, heroTag: heroTags[idx], fit: BoxFit.cover),
|
||||||
heroTag: heroTags[idx],
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
if (widget.data[idx]!.mediaType != SnMediaType.image) return;
|
|
||||||
context.pushTransparentRoute(
|
|
||||||
AttachmentZoomView(
|
|
||||||
data: widget.data.where((ele) => ele != null).cast(),
|
|
||||||
initialIndex: idx,
|
|
||||||
heroTags: heroTags,
|
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.black.withOpacity(0.7),
|
onTap: () {
|
||||||
rootNavigator: true,
|
if (widget.data[idx]!.mediaType != SnMediaType.image) return;
|
||||||
);
|
context.pushTransparentRoute(
|
||||||
},
|
AttachmentZoomView(
|
||||||
),
|
data: widget.data.where((ele) => ele != null).cast(),
|
||||||
)
|
initialIndex: idx,
|
||||||
.toList(),
|
heroTags: heroTags,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.black.withOpacity(0.7),
|
||||||
|
rootNavigator: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -163,43 +150,37 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
margin: widget.padding ?? EdgeInsets.zero,
|
margin: widget.padding ?? EdgeInsets.zero,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: backgroundColor,
|
color: backgroundColor,
|
||||||
border: Border(
|
border: Border(top: borderSide, bottom: borderSide),
|
||||||
top: borderSide,
|
|
||||||
bottom: borderSide,
|
|
||||||
),
|
|
||||||
borderRadius: AttachmentList.kDefaultRadius,
|
borderRadius: AttachmentList.kDefaultRadius,
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: AttachmentList.kDefaultRadius,
|
borderRadius: AttachmentList.kDefaultRadius,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: widget.data
|
children:
|
||||||
.mapIndexed(
|
widget.data
|
||||||
(idx, ele) => GestureDetector(
|
.mapIndexed(
|
||||||
child: AspectRatio(
|
(idx, ele) => GestureDetector(
|
||||||
aspectRatio: ele?.data['ratio']?.toDouble() ?? 1,
|
child: AspectRatio(
|
||||||
child: Container(
|
aspectRatio: ele?.data['ratio']?.toDouble() ?? 1,
|
||||||
constraints: constraints,
|
child: Container(
|
||||||
child: AttachmentItem(
|
constraints: constraints,
|
||||||
data: ele,
|
child: AttachmentItem(data: ele, heroTag: heroTags[idx], fit: BoxFit.cover),
|
||||||
heroTag: heroTags[idx],
|
),
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
),
|
.expand((ele) => [ele, const Divider(height: 1)])
|
||||||
)
|
.toList()
|
||||||
.expand((ele) => [ele, const Divider(height: 1)])
|
..removeLast(),
|
||||||
.toList()
|
|
||||||
..removeLast(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AspectRatio(
|
return Container(
|
||||||
aspectRatio: widget.data[0]?.data['ratio']?.toDouble() ?? 1,
|
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
|
||||||
child: Container(
|
child: AspectRatio(
|
||||||
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
|
aspectRatio: widget.data[0]?.data['ratio']?.toDouble() ?? 1,
|
||||||
child: ScrollConfiguration(
|
child: ScrollConfiguration(
|
||||||
behavior: _AttachmentListScrollBehavior(),
|
behavior: _AttachmentListScrollBehavior(),
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
@ -216,7 +197,8 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
if (widget.data[idx]?.mediaType != SnMediaType.image) return;
|
if (widget.data[idx]?.mediaType != SnMediaType.image) return;
|
||||||
context.pushTransparentRoute(
|
context.pushTransparentRoute(
|
||||||
AttachmentZoomView(
|
AttachmentZoomView(
|
||||||
data: widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(),
|
data:
|
||||||
|
widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(),
|
||||||
initialIndex: idx,
|
initialIndex: idx,
|
||||||
heroTags: heroTags,
|
heroTags: heroTags,
|
||||||
),
|
),
|
||||||
@ -230,26 +212,18 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: backgroundColor,
|
color: backgroundColor,
|
||||||
border: Border(
|
border: Border(top: borderSide, bottom: borderSide),
|
||||||
top: borderSide,
|
|
||||||
bottom: borderSide,
|
|
||||||
),
|
|
||||||
borderRadius: AttachmentList.kDefaultRadius,
|
borderRadius: AttachmentList.kDefaultRadius,
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: AttachmentList.kDefaultRadius,
|
borderRadius: AttachmentList.kDefaultRadius,
|
||||||
child: AttachmentItem(
|
child: AttachmentItem(data: widget.data[idx], heroTag: heroTags[idx]),
|
||||||
data: widget.data[idx],
|
|
||||||
heroTag: heroTags[idx],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 8,
|
right: 8,
|
||||||
bottom: 8,
|
bottom: 8,
|
||||||
child: Chip(
|
child: Chip(label: Text('${idx + 1}/${widget.data.length}')),
|
||||||
label: Text('${idx + 1}/${widget.data.length}'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -271,8 +245,5 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
|
|
||||||
class _AttachmentListScrollBehavior extends MaterialScrollBehavior {
|
class _AttachmentListScrollBehavior extends MaterialScrollBehavior {
|
||||||
@override
|
@override
|
||||||
Set<PointerDeviceKind> get dragDevices => {
|
Set<PointerDeviceKind> get dragDevices => {PointerDeviceKind.touch, PointerDeviceKind.mouse};
|
||||||
PointerDeviceKind.touch,
|
|
||||||
PointerDeviceKind.mouse,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -106,37 +106,30 @@ class ChatMessage extends StatelessWidget {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: AccountImage(
|
child: AccountImage(
|
||||||
content: user?.avatar,
|
content: user?.avatar,
|
||||||
badge: (user?.badges.isNotEmpty ?? false)
|
badge:
|
||||||
? Icon(
|
(user?.badges.isNotEmpty ?? false)
|
||||||
kBadgesMeta[user!.badges.first.type]?.$2 ??
|
? Icon(
|
||||||
Symbols.question_mark,
|
kBadgesMeta[user!.badges.first.type]?.$2 ?? Symbols.question_mark,
|
||||||
color: kBadgesMeta[user.badges.first.type]?.$3,
|
color: kBadgesMeta[user.badges.first.type]?.$3,
|
||||||
fill: 1,
|
fill: 1,
|
||||||
size: 18,
|
size: 18,
|
||||||
shadows: [
|
shadows: [
|
||||||
Shadow(
|
Shadow(offset: Offset(1, 1), blurRadius: 5.0, color: Color.fromARGB(200, 0, 0, 0)),
|
||||||
offset: Offset(1, 1),
|
],
|
||||||
blurRadius: 5.0,
|
)
|
||||||
color: Color.fromARGB(200, 0, 0, 0),
|
: null,
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
showPopover(
|
showPopover(
|
||||||
backgroundColor:
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
Theme.of(context).colorScheme.surface,
|
|
||||||
context: context,
|
context: context,
|
||||||
transition: PopoverTransition.other,
|
transition: PopoverTransition.other,
|
||||||
bodyBuilder: (context) => SizedBox(
|
bodyBuilder:
|
||||||
width: math.min(
|
(context) => SizedBox(
|
||||||
400, MediaQuery.of(context).size.width - 10),
|
width: math.min(400, MediaQuery.of(context).size.width - 10),
|
||||||
child: AccountPopoverCard(
|
child: AccountPopoverCard(data: user),
|
||||||
data: user,
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
direction: PopoverDirection.bottom,
|
direction: PopoverDirection.bottom,
|
||||||
arrowHeight: 5,
|
arrowHeight: 5,
|
||||||
arrowWidth: 15,
|
arrowWidth: 15,
|
||||||
@ -157,64 +150,46 @@ class ChatMessage extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (isCompact)
|
if (isCompact) AccountImage(content: user?.avatar, radius: 12).padding(right: 8),
|
||||||
AccountImage(
|
|
||||||
content: user?.avatar,
|
|
||||||
radius: 12,
|
|
||||||
).padding(right: 8),
|
|
||||||
Text(
|
Text(
|
||||||
(data.sender.nick?.isNotEmpty ?? false)
|
(data.sender.nick?.isNotEmpty ?? false) ? data.sender.nick! : user?.nick ?? 'unknown',
|
||||||
? data.sender.nick!
|
|
||||||
: user?.nick ?? 'unknown',
|
|
||||||
).bold(),
|
).bold(),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text(
|
Text(dateFormatter.format(data.createdAt.toLocal())).fontSize(13),
|
||||||
dateFormatter
|
|
||||||
.format(data.createdAt.toLocal()),
|
|
||||||
).fontSize(13),
|
|
||||||
],
|
],
|
||||||
).height(21),
|
).height(21),
|
||||||
if (isCompact) const Gap(8),
|
if (isCompact) const Gap(8),
|
||||||
if (data.preload?.quoteEvent != null)
|
if (data.preload?.quoteEvent != null)
|
||||||
StyledWidget(Container(
|
StyledWidget(
|
||||||
constraints: BoxConstraints(
|
Container(
|
||||||
maxWidth: 360,
|
constraints: BoxConstraints(maxWidth: 360),
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
borderRadius:
|
border: Border.all(color: Theme.of(context).dividerColor, width: 1),
|
||||||
const BorderRadius.all(Radius.circular(8)),
|
),
|
||||||
border: Border.all(
|
padding: const EdgeInsets.only(left: 4, right: 4, top: 8, bottom: 6),
|
||||||
color: Theme.of(context).dividerColor,
|
child: ChatMessage(
|
||||||
width: 1,
|
data: data.preload!.quoteEvent!,
|
||||||
|
isCompact: true,
|
||||||
|
onReply: onReply,
|
||||||
|
onEdit: onEdit,
|
||||||
|
onDelete: onDelete,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.only(
|
).padding(bottom: 4, top: 4),
|
||||||
left: 4,
|
|
||||||
right: 4,
|
|
||||||
top: 8,
|
|
||||||
bottom: 6,
|
|
||||||
),
|
|
||||||
child: ChatMessage(
|
|
||||||
data: data.preload!.quoteEvent!,
|
|
||||||
isCompact: true,
|
|
||||||
onReply: onReply,
|
|
||||||
onEdit: onEdit,
|
|
||||||
onDelete: onDelete,
|
|
||||||
),
|
|
||||||
)).padding(bottom: 4, top: 4),
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
'messages.new' => _ChatMessageText(
|
'messages.new' => _ChatMessageText(
|
||||||
data: data,
|
data: data,
|
||||||
onReply: onReply,
|
onReply: onReply,
|
||||||
onEdit: onEdit,
|
onEdit: onEdit,
|
||||||
onDelete: onDelete,
|
onDelete: onDelete,
|
||||||
),
|
),
|
||||||
_ => _ChatMessageSystemNotify(data: data),
|
_ => _ChatMessageSystemNotify(data: data),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
).opacity(isPending ? 0.5 : 1),
|
).opacity(isPending ? 0.5 : 1),
|
||||||
),
|
),
|
||||||
@ -222,22 +197,16 @@ class ChatMessage extends StatelessWidget {
|
|||||||
data.type == 'messages.new' &&
|
data.type == 'messages.new' &&
|
||||||
(data.body['text']?.isNotEmpty ?? false) &&
|
(data.body['text']?.isNotEmpty ?? false) &&
|
||||||
(cfg.prefs.getBool(kAppExpandChatLink) ?? true))
|
(cfg.prefs.getBool(kAppExpandChatLink) ?? true))
|
||||||
LinkPreviewWidget(text: data.body['text']!),
|
LinkPreviewWidget(text: data.body['text']!).padding(left: 48),
|
||||||
if (data.preload?.attachments?.isNotEmpty ?? false)
|
if (data.preload?.attachments?.isNotEmpty ?? false)
|
||||||
AttachmentList(
|
AttachmentList(
|
||||||
data: data.preload!.attachments!,
|
data: data.preload!.attachments!,
|
||||||
bordered: true,
|
bordered: true,
|
||||||
maxHeight: 360,
|
maxHeight: 360,
|
||||||
maxWidth: 480 - 48 - padding.left,
|
maxWidth: 480 - 48 - padding.left,
|
||||||
padding: padding.copyWith(
|
padding: padding.copyWith(top: 8, left: isCompact ? padding.left : 48 + padding.left),
|
||||||
top: 8,
|
|
||||||
left: isCompact ? padding.left : 48 + padding.left,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (!hasMerged && !isCompact)
|
if (!hasMerged && !isCompact) const Gap(12) else if (!isCompact) const Gap(8),
|
||||||
const Gap(12)
|
|
||||||
else if (!isCompact)
|
|
||||||
const Gap(8),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -251,8 +220,7 @@ class _ChatMessageText extends StatelessWidget {
|
|||||||
final Function(SnChatMessage)? onEdit;
|
final Function(SnChatMessage)? onEdit;
|
||||||
final Function(SnChatMessage)? onDelete;
|
final Function(SnChatMessage)? onDelete;
|
||||||
|
|
||||||
const _ChatMessageText(
|
const _ChatMessageText({required this.data, this.onReply, this.onEdit, this.onDelete});
|
||||||
{required this.data, this.onReply, this.onEdit, this.onDelete});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -266,8 +234,7 @@ class _ChatMessageText extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
SelectionArea(
|
SelectionArea(
|
||||||
contextMenuBuilder: (context, editableTextState) {
|
contextMenuBuilder: (context, editableTextState) {
|
||||||
final List<ContextMenuButtonItem> items =
|
final List<ContextMenuButtonItem> items = editableTextState.contextMenuButtonItems;
|
||||||
editableTextState.contextMenuButtonItems;
|
|
||||||
|
|
||||||
if (onReply != null) {
|
if (onReply != null) {
|
||||||
items.insert(
|
items.insert(
|
||||||
@ -314,14 +281,10 @@ class _ChatMessageText extends StatelessWidget {
|
|||||||
child: MarkdownTextContent(
|
child: MarkdownTextContent(
|
||||||
content: data.body['text'],
|
content: data.body['text'],
|
||||||
isAutoWarp: true,
|
isAutoWarp: true,
|
||||||
isEnlargeSticker:
|
isEnlargeSticker: RegExp(r"^:([-\w]+):$").hasMatch(data.body['text'] ?? ''),
|
||||||
RegExp(r"^:([-\w]+):$").hasMatch(data.body['text'] ?? ''),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (data.updatedAt != data.createdAt)
|
if (data.updatedAt != data.createdAt) Text('messageEditedHint'.tr()).fontSize(13).opacity(0.75),
|
||||||
Text(
|
|
||||||
'messageEditedHint'.tr(),
|
|
||||||
).fontSize(13).opacity(0.75),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else if (data.body['attachments']?.isNotEmpty) {
|
} else if (data.body['attachments']?.isNotEmpty) {
|
||||||
@ -329,11 +292,7 @@ class _ChatMessageText extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.file_present, size: 20),
|
const Icon(Symbols.file_present, size: 20),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text('messageFileHint'.plural(data.body['attachments']!.length)),
|
||||||
'messageFileHint'.plural(
|
|
||||||
data.body['attachments']!.length,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
).opacity(0.75);
|
).opacity(0.75);
|
||||||
}
|
}
|
||||||
@ -363,9 +322,7 @@ class _ChatMessageSystemNotify extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.edit, size: 20),
|
const Icon(Symbols.edit, size: 20),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text('messageEdited'.tr(args: ['#${data.relatedEventId}'])),
|
||||||
'messageEdited'.tr(args: ['#${data.relatedEventId}']),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
).opacity(0.75);
|
).opacity(0.75);
|
||||||
case 'messages.delete':
|
case 'messages.delete':
|
||||||
@ -373,31 +330,19 @@ class _ChatMessageSystemNotify extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.delete, size: 20),
|
const Icon(Symbols.delete, size: 20),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text('messageDeleted'.tr(args: ['#${data.relatedEventId}'])),
|
||||||
'messageDeleted'.tr(args: ['#${data.relatedEventId}']),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
).opacity(0.75);
|
).opacity(0.75);
|
||||||
case 'calls.start':
|
case 'calls.start':
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [const Icon(Symbols.call, size: 20), const Gap(4), Text('callMessageStarted'.tr())],
|
||||||
const Icon(Symbols.call, size: 20),
|
|
||||||
const Gap(4),
|
|
||||||
Text(
|
|
||||||
'callMessageStarted'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).opacity(0.75);
|
).opacity(0.75);
|
||||||
case 'calls.end':
|
case 'calls.end':
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.call_end, size: 20),
|
const Icon(Symbols.call_end, size: 20),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text('callMessageEnded'.tr(args: [_formatDuration(Duration(seconds: data.body['last']))])),
|
||||||
'callMessageEnded'.tr(args: [
|
|
||||||
_formatDuration(Duration(seconds: data.body['last'])),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
).opacity(0.75);
|
).opacity(0.75);
|
||||||
default:
|
default:
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
||||||
|
#include <fast_rsa/fast_rsa_plugin.h>
|
||||||
#include <file_saver/file_saver_plugin.h>
|
#include <file_saver/file_saver_plugin.h>
|
||||||
#include <file_selector_linux/file_selector_plugin.h>
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
#include <flutter_timezone/flutter_timezone_plugin.h>
|
#include <flutter_timezone/flutter_timezone_plugin.h>
|
||||||
@ -25,6 +26,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar =
|
g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin");
|
||||||
bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar);
|
bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) fast_rsa_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FastRsaPlugin");
|
||||||
|
fast_rsa_plugin_register_with_registrar(fast_rsa_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) file_saver_registrar =
|
g_autoptr(FlPluginRegistrar) file_saver_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
|
||||||
file_saver_plugin_register_with_registrar(file_saver_registrar);
|
file_saver_plugin_register_with_registrar(file_saver_registrar);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
bitsdojo_window_linux
|
bitsdojo_window_linux
|
||||||
|
fast_rsa
|
||||||
file_saver
|
file_saver
|
||||||
file_selector_linux
|
file_selector_linux
|
||||||
flutter_timezone
|
flutter_timezone
|
||||||
|
@ -8,6 +8,7 @@ import Foundation
|
|||||||
import bitsdojo_window_macos
|
import bitsdojo_window_macos
|
||||||
import connectivity_plus
|
import connectivity_plus
|
||||||
import device_info_plus
|
import device_info_plus
|
||||||
|
import fast_rsa
|
||||||
import file_picker
|
import file_picker
|
||||||
import file_saver
|
import file_saver
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
@ -43,6 +44,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin"))
|
BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin"))
|
||||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||||
|
FastRsaPlugin.register(with: registry.registrar(forPlugin: "FastRsaPlugin"))
|
||||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||||
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
|
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
|
16
pubspec.lock
16
pubspec.lock
@ -513,6 +513,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.2"
|
||||||
|
fast_rsa:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: fast_rsa
|
||||||
|
sha256: "205a36c0412b9fabebf3e18ccb5221d819cc28cfb3da988c0bf7b646368d0270"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.8.0"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -665,6 +673,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.70.2"
|
version: "0.70.2"
|
||||||
|
flat_buffers:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flat_buffers
|
||||||
|
sha256: "380bdcba5664a718bfd4ea20a45d39e13684f5318fcd8883066a55e21f37f4c3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "23.5.26"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -137,6 +137,7 @@ dependencies:
|
|||||||
flutter_timezone: ^4.1.0
|
flutter_timezone: ^4.1.0
|
||||||
flutter_map: ^8.1.0
|
flutter_map: ^8.1.0
|
||||||
geolocator: ^13.0.2
|
geolocator: ^13.0.2
|
||||||
|
fast_rsa: ^3.8.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
23
test/drift/my_database/generated/schema.dart
Normal file
23
test/drift/my_database/generated/schema.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE, DO NOT EDIT BY HAND.
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift/internal/migrations.dart';
|
||||||
|
import 'schema_v1.dart' as v1;
|
||||||
|
import 'schema_v2.dart' as v2;
|
||||||
|
|
||||||
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
|
@override
|
||||||
|
GeneratedDatabase databaseForVersion(QueryExecutor db, int version) {
|
||||||
|
switch (version) {
|
||||||
|
case 1:
|
||||||
|
return v1.DatabaseAtV1(db);
|
||||||
|
case 2:
|
||||||
|
return v2.DatabaseAtV2(db);
|
||||||
|
default:
|
||||||
|
throw MissingSchemaException(version, versions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const versions = const [1, 2];
|
||||||
|
}
|
462
test/drift/my_database/generated/schema_v1.dart
Normal file
462
test/drift/my_database/generated/schema_v1.dart
Normal file
@ -0,0 +1,462 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE, DO NOT EDIT BY HAND.
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
class SnLocalChatChannel extends Table
|
||||||
|
with TableInfo<SnLocalChatChannel, SnLocalChatChannelData> {
|
||||||
|
@override
|
||||||
|
final GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
SnLocalChatChannel(this.attachedDatabase, [this._alias]);
|
||||||
|
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||||
|
'id', aliasedName, false,
|
||||||
|
hasAutoIncrement: true,
|
||||||
|
type: DriftSqlType.int,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints:
|
||||||
|
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||||
|
late final GeneratedColumn<String> alias = GeneratedColumn<String>(
|
||||||
|
'alias', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
late final GeneratedColumn<String> content = GeneratedColumn<String>(
|
||||||
|
'content', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
|
||||||
|
'created_at', aliasedName, false,
|
||||||
|
type: DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: const CustomExpression(
|
||||||
|
'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)'));
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns => [id, alias, content, createdAt];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'sn_local_chat_channel';
|
||||||
|
@override
|
||||||
|
Set<GeneratedColumn> get $primaryKey => {id};
|
||||||
|
@override
|
||||||
|
SnLocalChatChannelData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return SnLocalChatChannelData(
|
||||||
|
id: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
||||||
|
alias: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}alias'])!,
|
||||||
|
content: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}content'])!,
|
||||||
|
createdAt: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
SnLocalChatChannel createAlias(String alias) {
|
||||||
|
return SnLocalChatChannel(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalChatChannelData extends DataClass
|
||||||
|
implements Insertable<SnLocalChatChannelData> {
|
||||||
|
final int id;
|
||||||
|
final String alias;
|
||||||
|
final String content;
|
||||||
|
final DateTime createdAt;
|
||||||
|
const SnLocalChatChannelData(
|
||||||
|
{required this.id,
|
||||||
|
required this.alias,
|
||||||
|
required this.content,
|
||||||
|
required this.createdAt});
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
map['id'] = Variable<int>(id);
|
||||||
|
map['alias'] = Variable<String>(alias);
|
||||||
|
map['content'] = Variable<String>(content);
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalChatChannelCompanion toCompanion(bool nullToAbsent) {
|
||||||
|
return SnLocalChatChannelCompanion(
|
||||||
|
id: Value(id),
|
||||||
|
alias: Value(alias),
|
||||||
|
content: Value(content),
|
||||||
|
createdAt: Value(createdAt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SnLocalChatChannelData.fromJson(Map<String, dynamic> json,
|
||||||
|
{ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return SnLocalChatChannelData(
|
||||||
|
id: serializer.fromJson<int>(json['id']),
|
||||||
|
alias: serializer.fromJson<String>(json['alias']),
|
||||||
|
content: serializer.fromJson<String>(json['content']),
|
||||||
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'id': serializer.toJson<int>(id),
|
||||||
|
'alias': serializer.toJson<String>(alias),
|
||||||
|
'content': serializer.toJson<String>(content),
|
||||||
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalChatChannelData copyWith(
|
||||||
|
{int? id, String? alias, String? content, DateTime? createdAt}) =>
|
||||||
|
SnLocalChatChannelData(
|
||||||
|
id: id ?? this.id,
|
||||||
|
alias: alias ?? this.alias,
|
||||||
|
content: content ?? this.content,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
);
|
||||||
|
SnLocalChatChannelData copyWithCompanion(SnLocalChatChannelCompanion data) {
|
||||||
|
return SnLocalChatChannelData(
|
||||||
|
id: data.id.present ? data.id.value : this.id,
|
||||||
|
alias: data.alias.present ? data.alias.value : this.alias,
|
||||||
|
content: data.content.present ? data.content.value : this.content,
|
||||||
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalChatChannelData(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('alias: $alias, ')
|
||||||
|
..write('content: $content, ')
|
||||||
|
..write('createdAt: $createdAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(id, alias, content, createdAt);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is SnLocalChatChannelData &&
|
||||||
|
other.id == this.id &&
|
||||||
|
other.alias == this.alias &&
|
||||||
|
other.content == this.content &&
|
||||||
|
other.createdAt == this.createdAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalChatChannelCompanion
|
||||||
|
extends UpdateCompanion<SnLocalChatChannelData> {
|
||||||
|
final Value<int> id;
|
||||||
|
final Value<String> alias;
|
||||||
|
final Value<String> content;
|
||||||
|
final Value<DateTime> createdAt;
|
||||||
|
const SnLocalChatChannelCompanion({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
this.alias = const Value.absent(),
|
||||||
|
this.content = const Value.absent(),
|
||||||
|
this.createdAt = const Value.absent(),
|
||||||
|
});
|
||||||
|
SnLocalChatChannelCompanion.insert({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
required String alias,
|
||||||
|
required String content,
|
||||||
|
this.createdAt = const Value.absent(),
|
||||||
|
}) : alias = Value(alias),
|
||||||
|
content = Value(content);
|
||||||
|
static Insertable<SnLocalChatChannelData> custom({
|
||||||
|
Expression<int>? id,
|
||||||
|
Expression<String>? alias,
|
||||||
|
Expression<String>? content,
|
||||||
|
Expression<DateTime>? createdAt,
|
||||||
|
}) {
|
||||||
|
return RawValuesInsertable({
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (alias != null) 'alias': alias,
|
||||||
|
if (content != null) 'content': content,
|
||||||
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalChatChannelCompanion copyWith(
|
||||||
|
{Value<int>? id,
|
||||||
|
Value<String>? alias,
|
||||||
|
Value<String>? content,
|
||||||
|
Value<DateTime>? createdAt}) {
|
||||||
|
return SnLocalChatChannelCompanion(
|
||||||
|
id: id ?? this.id,
|
||||||
|
alias: alias ?? this.alias,
|
||||||
|
content: content ?? this.content,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
if (id.present) {
|
||||||
|
map['id'] = Variable<int>(id.value);
|
||||||
|
}
|
||||||
|
if (alias.present) {
|
||||||
|
map['alias'] = Variable<String>(alias.value);
|
||||||
|
}
|
||||||
|
if (content.present) {
|
||||||
|
map['content'] = Variable<String>(content.value);
|
||||||
|
}
|
||||||
|
if (createdAt.present) {
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalChatChannelCompanion(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('alias: $alias, ')
|
||||||
|
..write('content: $content, ')
|
||||||
|
..write('createdAt: $createdAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalChatMessage extends Table
|
||||||
|
with TableInfo<SnLocalChatMessage, SnLocalChatMessageData> {
|
||||||
|
@override
|
||||||
|
final GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
SnLocalChatMessage(this.attachedDatabase, [this._alias]);
|
||||||
|
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||||
|
'id', aliasedName, false,
|
||||||
|
hasAutoIncrement: true,
|
||||||
|
type: DriftSqlType.int,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints:
|
||||||
|
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||||
|
late final GeneratedColumn<int> channelId = GeneratedColumn<int>(
|
||||||
|
'channel_id', aliasedName, false,
|
||||||
|
type: DriftSqlType.int, requiredDuringInsert: true);
|
||||||
|
late final GeneratedColumn<String> content = GeneratedColumn<String>(
|
||||||
|
'content', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
|
||||||
|
'created_at', aliasedName, false,
|
||||||
|
type: DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: const CustomExpression(
|
||||||
|
'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)'));
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns => [id, channelId, content, createdAt];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'sn_local_chat_message';
|
||||||
|
@override
|
||||||
|
Set<GeneratedColumn> get $primaryKey => {id};
|
||||||
|
@override
|
||||||
|
SnLocalChatMessageData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return SnLocalChatMessageData(
|
||||||
|
id: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
||||||
|
channelId: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}channel_id'])!,
|
||||||
|
content: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}content'])!,
|
||||||
|
createdAt: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
SnLocalChatMessage createAlias(String alias) {
|
||||||
|
return SnLocalChatMessage(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalChatMessageData extends DataClass
|
||||||
|
implements Insertable<SnLocalChatMessageData> {
|
||||||
|
final int id;
|
||||||
|
final int channelId;
|
||||||
|
final String content;
|
||||||
|
final DateTime createdAt;
|
||||||
|
const SnLocalChatMessageData(
|
||||||
|
{required this.id,
|
||||||
|
required this.channelId,
|
||||||
|
required this.content,
|
||||||
|
required this.createdAt});
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
map['id'] = Variable<int>(id);
|
||||||
|
map['channel_id'] = Variable<int>(channelId);
|
||||||
|
map['content'] = Variable<String>(content);
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalChatMessageCompanion toCompanion(bool nullToAbsent) {
|
||||||
|
return SnLocalChatMessageCompanion(
|
||||||
|
id: Value(id),
|
||||||
|
channelId: Value(channelId),
|
||||||
|
content: Value(content),
|
||||||
|
createdAt: Value(createdAt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SnLocalChatMessageData.fromJson(Map<String, dynamic> json,
|
||||||
|
{ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return SnLocalChatMessageData(
|
||||||
|
id: serializer.fromJson<int>(json['id']),
|
||||||
|
channelId: serializer.fromJson<int>(json['channelId']),
|
||||||
|
content: serializer.fromJson<String>(json['content']),
|
||||||
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'id': serializer.toJson<int>(id),
|
||||||
|
'channelId': serializer.toJson<int>(channelId),
|
||||||
|
'content': serializer.toJson<String>(content),
|
||||||
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalChatMessageData copyWith(
|
||||||
|
{int? id, int? channelId, String? content, DateTime? createdAt}) =>
|
||||||
|
SnLocalChatMessageData(
|
||||||
|
id: id ?? this.id,
|
||||||
|
channelId: channelId ?? this.channelId,
|
||||||
|
content: content ?? this.content,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
);
|
||||||
|
SnLocalChatMessageData copyWithCompanion(SnLocalChatMessageCompanion data) {
|
||||||
|
return SnLocalChatMessageData(
|
||||||
|
id: data.id.present ? data.id.value : this.id,
|
||||||
|
channelId: data.channelId.present ? data.channelId.value : this.channelId,
|
||||||
|
content: data.content.present ? data.content.value : this.content,
|
||||||
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalChatMessageData(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('channelId: $channelId, ')
|
||||||
|
..write('content: $content, ')
|
||||||
|
..write('createdAt: $createdAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(id, channelId, content, createdAt);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is SnLocalChatMessageData &&
|
||||||
|
other.id == this.id &&
|
||||||
|
other.channelId == this.channelId &&
|
||||||
|
other.content == this.content &&
|
||||||
|
other.createdAt == this.createdAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalChatMessageCompanion
|
||||||
|
extends UpdateCompanion<SnLocalChatMessageData> {
|
||||||
|
final Value<int> id;
|
||||||
|
final Value<int> channelId;
|
||||||
|
final Value<String> content;
|
||||||
|
final Value<DateTime> createdAt;
|
||||||
|
const SnLocalChatMessageCompanion({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
this.channelId = const Value.absent(),
|
||||||
|
this.content = const Value.absent(),
|
||||||
|
this.createdAt = const Value.absent(),
|
||||||
|
});
|
||||||
|
SnLocalChatMessageCompanion.insert({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
required int channelId,
|
||||||
|
required String content,
|
||||||
|
this.createdAt = const Value.absent(),
|
||||||
|
}) : channelId = Value(channelId),
|
||||||
|
content = Value(content);
|
||||||
|
static Insertable<SnLocalChatMessageData> custom({
|
||||||
|
Expression<int>? id,
|
||||||
|
Expression<int>? channelId,
|
||||||
|
Expression<String>? content,
|
||||||
|
Expression<DateTime>? createdAt,
|
||||||
|
}) {
|
||||||
|
return RawValuesInsertable({
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (channelId != null) 'channel_id': channelId,
|
||||||
|
if (content != null) 'content': content,
|
||||||
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalChatMessageCompanion copyWith(
|
||||||
|
{Value<int>? id,
|
||||||
|
Value<int>? channelId,
|
||||||
|
Value<String>? content,
|
||||||
|
Value<DateTime>? createdAt}) {
|
||||||
|
return SnLocalChatMessageCompanion(
|
||||||
|
id: id ?? this.id,
|
||||||
|
channelId: channelId ?? this.channelId,
|
||||||
|
content: content ?? this.content,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
if (id.present) {
|
||||||
|
map['id'] = Variable<int>(id.value);
|
||||||
|
}
|
||||||
|
if (channelId.present) {
|
||||||
|
map['channel_id'] = Variable<int>(channelId.value);
|
||||||
|
}
|
||||||
|
if (content.present) {
|
||||||
|
map['content'] = Variable<String>(content.value);
|
||||||
|
}
|
||||||
|
if (createdAt.present) {
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalChatMessageCompanion(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('channelId: $channelId, ')
|
||||||
|
..write('content: $content, ')
|
||||||
|
..write('createdAt: $createdAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DatabaseAtV1 extends GeneratedDatabase {
|
||||||
|
DatabaseAtV1(QueryExecutor e) : super(e);
|
||||||
|
late final SnLocalChatChannel snLocalChatChannel = SnLocalChatChannel(this);
|
||||||
|
late final SnLocalChatMessage snLocalChatMessage = SnLocalChatMessage(this);
|
||||||
|
@override
|
||||||
|
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||||
|
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||||
|
@override
|
||||||
|
List<DatabaseSchemaEntity> get allSchemaEntities =>
|
||||||
|
[snLocalChatChannel, snLocalChatMessage];
|
||||||
|
@override
|
||||||
|
int get schemaVersion => 1;
|
||||||
|
}
|
730
test/drift/my_database/generated/schema_v2.dart
Normal file
730
test/drift/my_database/generated/schema_v2.dart
Normal file
@ -0,0 +1,730 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// GENERATED CODE, DO NOT EDIT BY HAND.
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
class SnLocalChatChannel extends Table
|
||||||
|
with TableInfo<SnLocalChatChannel, SnLocalChatChannelData> {
|
||||||
|
@override
|
||||||
|
final GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
SnLocalChatChannel(this.attachedDatabase, [this._alias]);
|
||||||
|
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||||
|
'id', aliasedName, false,
|
||||||
|
hasAutoIncrement: true,
|
||||||
|
type: DriftSqlType.int,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints:
|
||||||
|
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||||
|
late final GeneratedColumn<String> alias = GeneratedColumn<String>(
|
||||||
|
'alias', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
late final GeneratedColumn<String> content = GeneratedColumn<String>(
|
||||||
|
'content', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
|
||||||
|
'created_at', aliasedName, false,
|
||||||
|
type: DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: const CustomExpression(
|
||||||
|
'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)'));
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns => [id, alias, content, createdAt];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'sn_local_chat_channel';
|
||||||
|
@override
|
||||||
|
Set<GeneratedColumn> get $primaryKey => {id};
|
||||||
|
@override
|
||||||
|
SnLocalChatChannelData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return SnLocalChatChannelData(
|
||||||
|
id: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
||||||
|
alias: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}alias'])!,
|
||||||
|
content: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}content'])!,
|
||||||
|
createdAt: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
SnLocalChatChannel createAlias(String alias) {
|
||||||
|
return SnLocalChatChannel(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalChatChannelData extends DataClass
|
||||||
|
implements Insertable<SnLocalChatChannelData> {
|
||||||
|
final int id;
|
||||||
|
final String alias;
|
||||||
|
final String content;
|
||||||
|
final DateTime createdAt;
|
||||||
|
const SnLocalChatChannelData(
|
||||||
|
{required this.id,
|
||||||
|
required this.alias,
|
||||||
|
required this.content,
|
||||||
|
required this.createdAt});
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
map['id'] = Variable<int>(id);
|
||||||
|
map['alias'] = Variable<String>(alias);
|
||||||
|
map['content'] = Variable<String>(content);
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalChatChannelCompanion toCompanion(bool nullToAbsent) {
|
||||||
|
return SnLocalChatChannelCompanion(
|
||||||
|
id: Value(id),
|
||||||
|
alias: Value(alias),
|
||||||
|
content: Value(content),
|
||||||
|
createdAt: Value(createdAt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SnLocalChatChannelData.fromJson(Map<String, dynamic> json,
|
||||||
|
{ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return SnLocalChatChannelData(
|
||||||
|
id: serializer.fromJson<int>(json['id']),
|
||||||
|
alias: serializer.fromJson<String>(json['alias']),
|
||||||
|
content: serializer.fromJson<String>(json['content']),
|
||||||
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'id': serializer.toJson<int>(id),
|
||||||
|
'alias': serializer.toJson<String>(alias),
|
||||||
|
'content': serializer.toJson<String>(content),
|
||||||
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalChatChannelData copyWith(
|
||||||
|
{int? id, String? alias, String? content, DateTime? createdAt}) =>
|
||||||
|
SnLocalChatChannelData(
|
||||||
|
id: id ?? this.id,
|
||||||
|
alias: alias ?? this.alias,
|
||||||
|
content: content ?? this.content,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
);
|
||||||
|
SnLocalChatChannelData copyWithCompanion(SnLocalChatChannelCompanion data) {
|
||||||
|
return SnLocalChatChannelData(
|
||||||
|
id: data.id.present ? data.id.value : this.id,
|
||||||
|
alias: data.alias.present ? data.alias.value : this.alias,
|
||||||
|
content: data.content.present ? data.content.value : this.content,
|
||||||
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalChatChannelData(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('alias: $alias, ')
|
||||||
|
..write('content: $content, ')
|
||||||
|
..write('createdAt: $createdAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(id, alias, content, createdAt);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is SnLocalChatChannelData &&
|
||||||
|
other.id == this.id &&
|
||||||
|
other.alias == this.alias &&
|
||||||
|
other.content == this.content &&
|
||||||
|
other.createdAt == this.createdAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalChatChannelCompanion
|
||||||
|
extends UpdateCompanion<SnLocalChatChannelData> {
|
||||||
|
final Value<int> id;
|
||||||
|
final Value<String> alias;
|
||||||
|
final Value<String> content;
|
||||||
|
final Value<DateTime> createdAt;
|
||||||
|
const SnLocalChatChannelCompanion({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
this.alias = const Value.absent(),
|
||||||
|
this.content = const Value.absent(),
|
||||||
|
this.createdAt = const Value.absent(),
|
||||||
|
});
|
||||||
|
SnLocalChatChannelCompanion.insert({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
required String alias,
|
||||||
|
required String content,
|
||||||
|
this.createdAt = const Value.absent(),
|
||||||
|
}) : alias = Value(alias),
|
||||||
|
content = Value(content);
|
||||||
|
static Insertable<SnLocalChatChannelData> custom({
|
||||||
|
Expression<int>? id,
|
||||||
|
Expression<String>? alias,
|
||||||
|
Expression<String>? content,
|
||||||
|
Expression<DateTime>? createdAt,
|
||||||
|
}) {
|
||||||
|
return RawValuesInsertable({
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (alias != null) 'alias': alias,
|
||||||
|
if (content != null) 'content': content,
|
||||||
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalChatChannelCompanion copyWith(
|
||||||
|
{Value<int>? id,
|
||||||
|
Value<String>? alias,
|
||||||
|
Value<String>? content,
|
||||||
|
Value<DateTime>? createdAt}) {
|
||||||
|
return SnLocalChatChannelCompanion(
|
||||||
|
id: id ?? this.id,
|
||||||
|
alias: alias ?? this.alias,
|
||||||
|
content: content ?? this.content,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
if (id.present) {
|
||||||
|
map['id'] = Variable<int>(id.value);
|
||||||
|
}
|
||||||
|
if (alias.present) {
|
||||||
|
map['alias'] = Variable<String>(alias.value);
|
||||||
|
}
|
||||||
|
if (content.present) {
|
||||||
|
map['content'] = Variable<String>(content.value);
|
||||||
|
}
|
||||||
|
if (createdAt.present) {
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalChatChannelCompanion(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('alias: $alias, ')
|
||||||
|
..write('content: $content, ')
|
||||||
|
..write('createdAt: $createdAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalChatMessage extends Table
|
||||||
|
with TableInfo<SnLocalChatMessage, SnLocalChatMessageData> {
|
||||||
|
@override
|
||||||
|
final GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
SnLocalChatMessage(this.attachedDatabase, [this._alias]);
|
||||||
|
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||||
|
'id', aliasedName, false,
|
||||||
|
hasAutoIncrement: true,
|
||||||
|
type: DriftSqlType.int,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints:
|
||||||
|
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||||
|
late final GeneratedColumn<int> channelId = GeneratedColumn<int>(
|
||||||
|
'channel_id', aliasedName, false,
|
||||||
|
type: DriftSqlType.int, requiredDuringInsert: true);
|
||||||
|
late final GeneratedColumn<String> content = GeneratedColumn<String>(
|
||||||
|
'content', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
|
||||||
|
'created_at', aliasedName, false,
|
||||||
|
type: DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: const CustomExpression(
|
||||||
|
'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)'));
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns => [id, channelId, content, createdAt];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'sn_local_chat_message';
|
||||||
|
@override
|
||||||
|
Set<GeneratedColumn> get $primaryKey => {id};
|
||||||
|
@override
|
||||||
|
SnLocalChatMessageData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return SnLocalChatMessageData(
|
||||||
|
id: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
||||||
|
channelId: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}channel_id'])!,
|
||||||
|
content: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}content'])!,
|
||||||
|
createdAt: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
SnLocalChatMessage createAlias(String alias) {
|
||||||
|
return SnLocalChatMessage(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalChatMessageData extends DataClass
|
||||||
|
implements Insertable<SnLocalChatMessageData> {
|
||||||
|
final int id;
|
||||||
|
final int channelId;
|
||||||
|
final String content;
|
||||||
|
final DateTime createdAt;
|
||||||
|
const SnLocalChatMessageData(
|
||||||
|
{required this.id,
|
||||||
|
required this.channelId,
|
||||||
|
required this.content,
|
||||||
|
required this.createdAt});
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
map['id'] = Variable<int>(id);
|
||||||
|
map['channel_id'] = Variable<int>(channelId);
|
||||||
|
map['content'] = Variable<String>(content);
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalChatMessageCompanion toCompanion(bool nullToAbsent) {
|
||||||
|
return SnLocalChatMessageCompanion(
|
||||||
|
id: Value(id),
|
||||||
|
channelId: Value(channelId),
|
||||||
|
content: Value(content),
|
||||||
|
createdAt: Value(createdAt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SnLocalChatMessageData.fromJson(Map<String, dynamic> json,
|
||||||
|
{ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return SnLocalChatMessageData(
|
||||||
|
id: serializer.fromJson<int>(json['id']),
|
||||||
|
channelId: serializer.fromJson<int>(json['channelId']),
|
||||||
|
content: serializer.fromJson<String>(json['content']),
|
||||||
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'id': serializer.toJson<int>(id),
|
||||||
|
'channelId': serializer.toJson<int>(channelId),
|
||||||
|
'content': serializer.toJson<String>(content),
|
||||||
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalChatMessageData copyWith(
|
||||||
|
{int? id, int? channelId, String? content, DateTime? createdAt}) =>
|
||||||
|
SnLocalChatMessageData(
|
||||||
|
id: id ?? this.id,
|
||||||
|
channelId: channelId ?? this.channelId,
|
||||||
|
content: content ?? this.content,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
);
|
||||||
|
SnLocalChatMessageData copyWithCompanion(SnLocalChatMessageCompanion data) {
|
||||||
|
return SnLocalChatMessageData(
|
||||||
|
id: data.id.present ? data.id.value : this.id,
|
||||||
|
channelId: data.channelId.present ? data.channelId.value : this.channelId,
|
||||||
|
content: data.content.present ? data.content.value : this.content,
|
||||||
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalChatMessageData(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('channelId: $channelId, ')
|
||||||
|
..write('content: $content, ')
|
||||||
|
..write('createdAt: $createdAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(id, channelId, content, createdAt);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is SnLocalChatMessageData &&
|
||||||
|
other.id == this.id &&
|
||||||
|
other.channelId == this.channelId &&
|
||||||
|
other.content == this.content &&
|
||||||
|
other.createdAt == this.createdAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalChatMessageCompanion
|
||||||
|
extends UpdateCompanion<SnLocalChatMessageData> {
|
||||||
|
final Value<int> id;
|
||||||
|
final Value<int> channelId;
|
||||||
|
final Value<String> content;
|
||||||
|
final Value<DateTime> createdAt;
|
||||||
|
const SnLocalChatMessageCompanion({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
this.channelId = const Value.absent(),
|
||||||
|
this.content = const Value.absent(),
|
||||||
|
this.createdAt = const Value.absent(),
|
||||||
|
});
|
||||||
|
SnLocalChatMessageCompanion.insert({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
required int channelId,
|
||||||
|
required String content,
|
||||||
|
this.createdAt = const Value.absent(),
|
||||||
|
}) : channelId = Value(channelId),
|
||||||
|
content = Value(content);
|
||||||
|
static Insertable<SnLocalChatMessageData> custom({
|
||||||
|
Expression<int>? id,
|
||||||
|
Expression<int>? channelId,
|
||||||
|
Expression<String>? content,
|
||||||
|
Expression<DateTime>? createdAt,
|
||||||
|
}) {
|
||||||
|
return RawValuesInsertable({
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (channelId != null) 'channel_id': channelId,
|
||||||
|
if (content != null) 'content': content,
|
||||||
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalChatMessageCompanion copyWith(
|
||||||
|
{Value<int>? id,
|
||||||
|
Value<int>? channelId,
|
||||||
|
Value<String>? content,
|
||||||
|
Value<DateTime>? createdAt}) {
|
||||||
|
return SnLocalChatMessageCompanion(
|
||||||
|
id: id ?? this.id,
|
||||||
|
channelId: channelId ?? this.channelId,
|
||||||
|
content: content ?? this.content,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
if (id.present) {
|
||||||
|
map['id'] = Variable<int>(id.value);
|
||||||
|
}
|
||||||
|
if (channelId.present) {
|
||||||
|
map['channel_id'] = Variable<int>(channelId.value);
|
||||||
|
}
|
||||||
|
if (content.present) {
|
||||||
|
map['content'] = Variable<String>(content.value);
|
||||||
|
}
|
||||||
|
if (createdAt.present) {
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalChatMessageCompanion(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('channelId: $channelId, ')
|
||||||
|
..write('content: $content, ')
|
||||||
|
..write('createdAt: $createdAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalKeyPair extends Table
|
||||||
|
with TableInfo<SnLocalKeyPair, SnLocalKeyPairData> {
|
||||||
|
@override
|
||||||
|
final GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
SnLocalKeyPair(this.attachedDatabase, [this._alias]);
|
||||||
|
late final GeneratedColumn<String> id = GeneratedColumn<String>(
|
||||||
|
'id', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
late final GeneratedColumn<int> accountId = GeneratedColumn<int>(
|
||||||
|
'account_id', aliasedName, false,
|
||||||
|
type: DriftSqlType.int, requiredDuringInsert: true);
|
||||||
|
late final GeneratedColumn<String> publicKey = GeneratedColumn<String>(
|
||||||
|
'public_key', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
late final GeneratedColumn<String> privateKey = GeneratedColumn<String>(
|
||||||
|
'private_key', aliasedName, true,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: false);
|
||||||
|
late final GeneratedColumn<bool> isActive = GeneratedColumn<bool>(
|
||||||
|
'is_active', aliasedName, false,
|
||||||
|
type: DriftSqlType.bool,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints:
|
||||||
|
GeneratedColumn.constraintIsAlways('CHECK ("is_active" IN (0, 1))'),
|
||||||
|
defaultValue: const CustomExpression('0'));
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns =>
|
||||||
|
[id, accountId, publicKey, privateKey, isActive];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'sn_local_key_pair';
|
||||||
|
@override
|
||||||
|
Set<GeneratedColumn> get $primaryKey => const {};
|
||||||
|
@override
|
||||||
|
SnLocalKeyPairData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return SnLocalKeyPairData(
|
||||||
|
id: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||||
|
accountId: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}account_id'])!,
|
||||||
|
publicKey: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}public_key'])!,
|
||||||
|
privateKey: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}private_key']),
|
||||||
|
isActive: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.bool, data['${effectivePrefix}is_active'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
SnLocalKeyPair createAlias(String alias) {
|
||||||
|
return SnLocalKeyPair(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalKeyPairData extends DataClass
|
||||||
|
implements Insertable<SnLocalKeyPairData> {
|
||||||
|
final String id;
|
||||||
|
final int accountId;
|
||||||
|
final String publicKey;
|
||||||
|
final String? privateKey;
|
||||||
|
final bool isActive;
|
||||||
|
const SnLocalKeyPairData(
|
||||||
|
{required this.id,
|
||||||
|
required this.accountId,
|
||||||
|
required this.publicKey,
|
||||||
|
this.privateKey,
|
||||||
|
required this.isActive});
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
map['id'] = Variable<String>(id);
|
||||||
|
map['account_id'] = Variable<int>(accountId);
|
||||||
|
map['public_key'] = Variable<String>(publicKey);
|
||||||
|
if (!nullToAbsent || privateKey != null) {
|
||||||
|
map['private_key'] = Variable<String>(privateKey);
|
||||||
|
}
|
||||||
|
map['is_active'] = Variable<bool>(isActive);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalKeyPairCompanion toCompanion(bool nullToAbsent) {
|
||||||
|
return SnLocalKeyPairCompanion(
|
||||||
|
id: Value(id),
|
||||||
|
accountId: Value(accountId),
|
||||||
|
publicKey: Value(publicKey),
|
||||||
|
privateKey: privateKey == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(privateKey),
|
||||||
|
isActive: Value(isActive),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SnLocalKeyPairData.fromJson(Map<String, dynamic> json,
|
||||||
|
{ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return SnLocalKeyPairData(
|
||||||
|
id: serializer.fromJson<String>(json['id']),
|
||||||
|
accountId: serializer.fromJson<int>(json['accountId']),
|
||||||
|
publicKey: serializer.fromJson<String>(json['publicKey']),
|
||||||
|
privateKey: serializer.fromJson<String?>(json['privateKey']),
|
||||||
|
isActive: serializer.fromJson<bool>(json['isActive']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'id': serializer.toJson<String>(id),
|
||||||
|
'accountId': serializer.toJson<int>(accountId),
|
||||||
|
'publicKey': serializer.toJson<String>(publicKey),
|
||||||
|
'privateKey': serializer.toJson<String?>(privateKey),
|
||||||
|
'isActive': serializer.toJson<bool>(isActive),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalKeyPairData copyWith(
|
||||||
|
{String? id,
|
||||||
|
int? accountId,
|
||||||
|
String? publicKey,
|
||||||
|
Value<String?> privateKey = const Value.absent(),
|
||||||
|
bool? isActive}) =>
|
||||||
|
SnLocalKeyPairData(
|
||||||
|
id: id ?? this.id,
|
||||||
|
accountId: accountId ?? this.accountId,
|
||||||
|
publicKey: publicKey ?? this.publicKey,
|
||||||
|
privateKey: privateKey.present ? privateKey.value : this.privateKey,
|
||||||
|
isActive: isActive ?? this.isActive,
|
||||||
|
);
|
||||||
|
SnLocalKeyPairData copyWithCompanion(SnLocalKeyPairCompanion data) {
|
||||||
|
return SnLocalKeyPairData(
|
||||||
|
id: data.id.present ? data.id.value : this.id,
|
||||||
|
accountId: data.accountId.present ? data.accountId.value : this.accountId,
|
||||||
|
publicKey: data.publicKey.present ? data.publicKey.value : this.publicKey,
|
||||||
|
privateKey:
|
||||||
|
data.privateKey.present ? data.privateKey.value : this.privateKey,
|
||||||
|
isActive: data.isActive.present ? data.isActive.value : this.isActive,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalKeyPairData(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('accountId: $accountId, ')
|
||||||
|
..write('publicKey: $publicKey, ')
|
||||||
|
..write('privateKey: $privateKey, ')
|
||||||
|
..write('isActive: $isActive')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
Object.hash(id, accountId, publicKey, privateKey, isActive);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is SnLocalKeyPairData &&
|
||||||
|
other.id == this.id &&
|
||||||
|
other.accountId == this.accountId &&
|
||||||
|
other.publicKey == this.publicKey &&
|
||||||
|
other.privateKey == this.privateKey &&
|
||||||
|
other.isActive == this.isActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnLocalKeyPairCompanion extends UpdateCompanion<SnLocalKeyPairData> {
|
||||||
|
final Value<String> id;
|
||||||
|
final Value<int> accountId;
|
||||||
|
final Value<String> publicKey;
|
||||||
|
final Value<String?> privateKey;
|
||||||
|
final Value<bool> isActive;
|
||||||
|
final Value<int> rowid;
|
||||||
|
const SnLocalKeyPairCompanion({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
this.accountId = const Value.absent(),
|
||||||
|
this.publicKey = const Value.absent(),
|
||||||
|
this.privateKey = const Value.absent(),
|
||||||
|
this.isActive = const Value.absent(),
|
||||||
|
this.rowid = const Value.absent(),
|
||||||
|
});
|
||||||
|
SnLocalKeyPairCompanion.insert({
|
||||||
|
required String id,
|
||||||
|
required int accountId,
|
||||||
|
required String publicKey,
|
||||||
|
this.privateKey = const Value.absent(),
|
||||||
|
this.isActive = const Value.absent(),
|
||||||
|
this.rowid = const Value.absent(),
|
||||||
|
}) : id = Value(id),
|
||||||
|
accountId = Value(accountId),
|
||||||
|
publicKey = Value(publicKey);
|
||||||
|
static Insertable<SnLocalKeyPairData> custom({
|
||||||
|
Expression<String>? id,
|
||||||
|
Expression<int>? accountId,
|
||||||
|
Expression<String>? publicKey,
|
||||||
|
Expression<String>? privateKey,
|
||||||
|
Expression<bool>? isActive,
|
||||||
|
Expression<int>? rowid,
|
||||||
|
}) {
|
||||||
|
return RawValuesInsertable({
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (accountId != null) 'account_id': accountId,
|
||||||
|
if (publicKey != null) 'public_key': publicKey,
|
||||||
|
if (privateKey != null) 'private_key': privateKey,
|
||||||
|
if (isActive != null) 'is_active': isActive,
|
||||||
|
if (rowid != null) 'rowid': rowid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SnLocalKeyPairCompanion copyWith(
|
||||||
|
{Value<String>? id,
|
||||||
|
Value<int>? accountId,
|
||||||
|
Value<String>? publicKey,
|
||||||
|
Value<String?>? privateKey,
|
||||||
|
Value<bool>? isActive,
|
||||||
|
Value<int>? rowid}) {
|
||||||
|
return SnLocalKeyPairCompanion(
|
||||||
|
id: id ?? this.id,
|
||||||
|
accountId: accountId ?? this.accountId,
|
||||||
|
publicKey: publicKey ?? this.publicKey,
|
||||||
|
privateKey: privateKey ?? this.privateKey,
|
||||||
|
isActive: isActive ?? this.isActive,
|
||||||
|
rowid: rowid ?? this.rowid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
if (id.present) {
|
||||||
|
map['id'] = Variable<String>(id.value);
|
||||||
|
}
|
||||||
|
if (accountId.present) {
|
||||||
|
map['account_id'] = Variable<int>(accountId.value);
|
||||||
|
}
|
||||||
|
if (publicKey.present) {
|
||||||
|
map['public_key'] = Variable<String>(publicKey.value);
|
||||||
|
}
|
||||||
|
if (privateKey.present) {
|
||||||
|
map['private_key'] = Variable<String>(privateKey.value);
|
||||||
|
}
|
||||||
|
if (isActive.present) {
|
||||||
|
map['is_active'] = Variable<bool>(isActive.value);
|
||||||
|
}
|
||||||
|
if (rowid.present) {
|
||||||
|
map['rowid'] = Variable<int>(rowid.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SnLocalKeyPairCompanion(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('accountId: $accountId, ')
|
||||||
|
..write('publicKey: $publicKey, ')
|
||||||
|
..write('privateKey: $privateKey, ')
|
||||||
|
..write('isActive: $isActive, ')
|
||||||
|
..write('rowid: $rowid')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DatabaseAtV2 extends GeneratedDatabase {
|
||||||
|
DatabaseAtV2(QueryExecutor e) : super(e);
|
||||||
|
late final SnLocalChatChannel snLocalChatChannel = SnLocalChatChannel(this);
|
||||||
|
late final SnLocalChatMessage snLocalChatMessage = SnLocalChatMessage(this);
|
||||||
|
late final SnLocalKeyPair snLocalKeyPair = SnLocalKeyPair(this);
|
||||||
|
@override
|
||||||
|
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||||
|
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||||
|
@override
|
||||||
|
List<DatabaseSchemaEntity> get allSchemaEntities =>
|
||||||
|
[snLocalChatChannel, snLocalChatMessage, snLocalKeyPair];
|
||||||
|
@override
|
||||||
|
int get schemaVersion => 2;
|
||||||
|
}
|
75
test/drift/my_database/migration_test.dart
Normal file
75
test/drift/my_database/migration_test.dart
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// ignore_for_file: unused_local_variable, unused_import
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_dev/api/migrations_native.dart';
|
||||||
|
import 'package:surface/database/database.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'generated/schema.dart';
|
||||||
|
|
||||||
|
import 'generated/schema_v1.dart' as v1;
|
||||||
|
import 'generated/schema_v2.dart' as v2;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||||
|
late SchemaVerifier verifier;
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
verifier = SchemaVerifier(GeneratedHelper());
|
||||||
|
});
|
||||||
|
|
||||||
|
group('simple database migrations', () {
|
||||||
|
// These simple tests verify all possible schema updates with a simple (no
|
||||||
|
// data) migration. This is a quick way to ensure that written database
|
||||||
|
// migrations properly alter the schema.
|
||||||
|
const versions = GeneratedHelper.versions;
|
||||||
|
for (final (i, fromVersion) in versions.indexed) {
|
||||||
|
group('from $fromVersion', () {
|
||||||
|
for (final toVersion in versions.skip(i + 1)) {
|
||||||
|
test('to $toVersion', () async {
|
||||||
|
final schema = await verifier.schemaAt(fromVersion);
|
||||||
|
final db = AppDatabase(schema.newConnection());
|
||||||
|
await verifier.migrateAndValidate(db, toVersion);
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// The following template shows how to write tests ensuring your migrations
|
||||||
|
// preserve existing data.
|
||||||
|
// Testing this can be useful for migrations that change existing columns
|
||||||
|
// (e.g. by alterating their type or constraints). Migrations that only add
|
||||||
|
// tables or columns typically don't need these advanced tests. For more
|
||||||
|
// information, see https://drift.simonbinder.eu/migrations/tests/#verifying-data-integrity
|
||||||
|
// TODO: This generated template shows how these tests could be written. Adopt
|
||||||
|
// it to your own needs when testing migrations with data integrity.
|
||||||
|
test('migration from v1 to v2 does not corrupt data', () async {
|
||||||
|
// Add data to insert into the old database, and the expected rows after the
|
||||||
|
// migration.
|
||||||
|
// TODO: Fill these lists
|
||||||
|
final oldSnLocalChatChannelData = <v1.SnLocalChatChannelData>[];
|
||||||
|
final expectedNewSnLocalChatChannelData = <v2.SnLocalChatChannelData>[];
|
||||||
|
|
||||||
|
final oldSnLocalChatMessageData = <v1.SnLocalChatMessageData>[];
|
||||||
|
final expectedNewSnLocalChatMessageData = <v2.SnLocalChatMessageData>[];
|
||||||
|
|
||||||
|
await verifier.testWithDataIntegrity(
|
||||||
|
oldVersion: 1,
|
||||||
|
newVersion: 2,
|
||||||
|
createOld: v1.DatabaseAtV1.new,
|
||||||
|
createNew: v2.DatabaseAtV2.new,
|
||||||
|
openTestedDatabase: AppDatabase.new,
|
||||||
|
createItems: (batch, oldDb) {
|
||||||
|
batch.insertAll(oldDb.snLocalChatChannel, oldSnLocalChatChannelData);
|
||||||
|
batch.insertAll(oldDb.snLocalChatMessage, oldSnLocalChatMessageData);
|
||||||
|
},
|
||||||
|
validateItems: (newDb) async {
|
||||||
|
expect(expectedNewSnLocalChatChannelData,
|
||||||
|
await newDb.select(newDb.snLocalChatChannel).get());
|
||||||
|
expect(expectedNewSnLocalChatMessageData,
|
||||||
|
await newDb.select(newDb.snLocalChatMessage).get());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
|
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
|
||||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||||
|
#include <fast_rsa/fast_rsa_plugin.h>
|
||||||
#include <file_saver/file_saver_plugin.h>
|
#include <file_saver/file_saver_plugin.h>
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
@ -35,6 +36,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
|
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
|
||||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||||
|
FastRsaPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FastRsaPlugin"));
|
||||||
FileSaverPluginRegisterWithRegistrar(
|
FileSaverPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FileSaverPlugin"));
|
registry->GetRegistrarForPlugin("FileSaverPlugin"));
|
||||||
FileSelectorWindowsRegisterWithRegistrar(
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
bitsdojo_window_windows
|
bitsdojo_window_windows
|
||||||
connectivity_plus
|
connectivity_plus
|
||||||
|
fast_rsa
|
||||||
file_saver
|
file_saver
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
firebase_core
|
firebase_core
|
||||||
|
Loading…
x
Reference in New Issue
Block a user