fixing multiple use-ability issues #343 #342
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run

This commit is contained in:
otsmr 2025-12-21 00:06:10 +01:00
parent 9fe55ab62d
commit 074ead8b4f
15 changed files with 6895 additions and 43 deletions

View file

@ -42,8 +42,15 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
.getSingleOrNull();
}
Future<List<Contact>> getContactsByUsername(String username) async {
return (select(contacts)..where((t) => t.username.equals(username))).get();
Future<List<Contact>> getContactsByUsername(
String username, {
String username2 = '_______',
}) async {
return (select(contacts)
..where(
(t) => t.username.equals(username) | t.username.equals(username2),
))
.get();
}
Future<void> deleteContactByUserId(int userId) {
@ -58,7 +65,8 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
.write(updatedValues);
if (updatedValues.blocked.present ||
updatedValues.displayName.present ||
updatedValues.nickName.present) {
updatedValues.nickName.present ||
updatedValues.username.present) {
final contact = await getContactByUserId(userId).getSingleOrNull();
if (contact != null) {
await updatePushUser(contact);
@ -118,7 +126,12 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
Stream<List<Contact>> watchAllAcceptedContacts() {
return (select(contacts)
..where((t) => t.blocked.equals(false) & t.accepted.equals(true)))
..where(
(t) =>
t.blocked.equals(false) &
t.accepted.equals(true) &
t.accountDeleted.equals(false),
))
.watch();
}

View file

@ -386,14 +386,18 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
int getFlameCounterFromGroup(Group? group) {
if (group == null) return 0;
if (group.lastMessageSend == null || group.lastMessageReceived == null) {
if (group.lastMessageSend == null ||
group.lastMessageReceived == null ||
group.lastFlameCounterChange == null) {
return 0;
}
final now = DateTime.now();
final startOfToday = DateTime(now.year, now.month, now.day);
final twoDaysAgo = startOfToday.subtract(const Duration(days: 2));
final oneDayAgo = startOfToday.subtract(const Duration(days: 1));
if (group.lastMessageSend!.isAfter(twoDaysAgo) &&
group.lastMessageReceived!.isAfter(twoDaysAgo)) {
group.lastMessageReceived!.isAfter(twoDaysAgo) ||
group.lastFlameCounterChange!.isAfter(oneDayAgo)) {
return group.flameCounter + 1;
} else {
return 0;

File diff suppressed because one or more lines are too long

View file

@ -90,8 +90,7 @@ class GroupHistories extends Table {
IntColumn get contactId =>
integer().nullable().references(Contacts, #userId)();
IntColumn get affectedContactId =>
integer().nullable().references(Contacts, #userId)();
IntColumn get affectedContactId => integer().nullable()();
TextColumn get oldGroupName => text().nullable()();
TextColumn get newGroupName => text().nullable()();

View file

@ -67,7 +67,7 @@ class TwonlyDB extends _$TwonlyDB {
TwonlyDB.forTesting(DatabaseConnection super.connection);
@override
int get schemaVersion => 3;
int get schemaVersion => 4;
static QueryExecutor _openConnection() {
return driftDatabase(
@ -92,6 +92,17 @@ class TwonlyDB extends _$TwonlyDB {
from2To3: (m, schema) async {
await m.addColumn(schema.groups, schema.groups.draftMessage);
},
from3To4: (m, schema) async {
await m.alterTable(
TableMigration(
schema.groupHistories,
columnTransformer: {
schema.groupHistories.affectedContactId:
schema.groupHistories.affectedContactId,
},
),
);
},
),
);
}

View file

@ -1566,9 +1566,390 @@ class Shape17 extends i0.VersionedTable {
i1.GeneratedColumn<String> _column_100(String aliasedName) =>
i1.GeneratedColumn<String>('draft_message', aliasedName, true,
type: i1.DriftSqlType.string);
final class Schema4 extends i0.VersionedSchema {
Schema4({required super.database}) : super(version: 4);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
contacts,
groups,
mediaFiles,
messages,
messageHistories,
reactions,
groupMembers,
receipts,
receivedReceipts,
signalIdentityKeyStores,
signalPreKeyStores,
signalSenderKeyStores,
signalSessionStores,
signalContactPreKeys,
signalContactSignedPreKeys,
messageActions,
groupHistories,
];
late final Shape0 contacts = Shape0(
source: i0.VersionedTable(
entityName: 'contacts',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(user_id)',
],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_4,
_column_5,
_column_6,
_column_7,
_column_8,
_column_9,
_column_10,
_column_11,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape17 groups = Shape17(
source: i0.VersionedTable(
entityName: 'groups',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(group_id)',
],
columns: [
_column_13,
_column_14,
_column_15,
_column_16,
_column_17,
_column_18,
_column_19,
_column_20,
_column_21,
_column_22,
_column_23,
_column_24,
_column_100,
_column_25,
_column_26,
_column_27,
_column_12,
_column_28,
_column_29,
_column_30,
_column_31,
_column_32,
_column_33,
_column_34,
_column_35,
],
attachedDatabase: database,
),
alias: null);
late final Shape2 mediaFiles = Shape2(
source: i0.VersionedTable(
entityName: 'media_files',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(media_id)',
],
columns: [
_column_36,
_column_37,
_column_38,
_column_39,
_column_40,
_column_41,
_column_42,
_column_43,
_column_44,
_column_45,
_column_46,
_column_47,
_column_48,
_column_49,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape3 messages = Shape3(
source: i0.VersionedTable(
entityName: 'messages',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(message_id)',
],
columns: [
_column_50,
_column_51,
_column_52,
_column_37,
_column_53,
_column_54,
_column_55,
_column_56,
_column_46,
_column_57,
_column_58,
_column_59,
_column_60,
_column_12,
_column_61,
_column_62,
_column_63,
],
attachedDatabase: database,
),
alias: null);
late final Shape4 messageHistories = Shape4(
source: i0.VersionedTable(
entityName: 'message_histories',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_64,
_column_65,
_column_66,
_column_53,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 reactions = Shape5(
source: i0.VersionedTable(
entityName: 'reactions',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(message_id, sender_id, emoji)',
],
columns: [
_column_65,
_column_67,
_column_68,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape6 groupMembers = Shape6(
source: i0.VersionedTable(
entityName: 'group_members',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(group_id, contact_id)',
],
columns: [
_column_50,
_column_69,
_column_70,
_column_71,
_column_72,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape7 receipts = Shape7(
source: i0.VersionedTable(
entityName: 'receipts',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(receipt_id)',
],
columns: [
_column_73,
_column_74,
_column_75,
_column_76,
_column_77,
_column_78,
_column_79,
_column_80,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape8 receivedReceipts = Shape8(
source: i0.VersionedTable(
entityName: 'received_receipts',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(receipt_id)',
],
columns: [
_column_73,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape9 signalIdentityKeyStores = Shape9(
source: i0.VersionedTable(
entityName: 'signal_identity_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(device_id, name)',
],
columns: [
_column_81,
_column_82,
_column_83,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape10 signalPreKeyStores = Shape10(
source: i0.VersionedTable(
entityName: 'signal_pre_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(pre_key_id)',
],
columns: [
_column_84,
_column_85,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape11 signalSenderKeyStores = Shape11(
source: i0.VersionedTable(
entityName: 'signal_sender_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(sender_key_name)',
],
columns: [
_column_86,
_column_87,
],
attachedDatabase: database,
),
alias: null);
late final Shape12 signalSessionStores = Shape12(
source: i0.VersionedTable(
entityName: 'signal_session_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(device_id, name)',
],
columns: [
_column_81,
_column_82,
_column_88,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape13 signalContactPreKeys = Shape13(
source: i0.VersionedTable(
entityName: 'signal_contact_pre_keys',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(contact_id, pre_key_id)',
],
columns: [
_column_74,
_column_84,
_column_85,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape14 signalContactSignedPreKeys = Shape14(
source: i0.VersionedTable(
entityName: 'signal_contact_signed_pre_keys',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(contact_id)',
],
columns: [
_column_74,
_column_89,
_column_90,
_column_91,
_column_12,
],
attachedDatabase: database,
),
alias: null);
late final Shape15 messageActions = Shape15(
source: i0.VersionedTable(
entityName: 'message_actions',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(message_id, contact_id, type)',
],
columns: [
_column_65,
_column_92,
_column_37,
_column_93,
],
attachedDatabase: database,
),
alias: null);
late final Shape16 groupHistories = Shape16(
source: i0.VersionedTable(
entityName: 'group_histories',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(group_history_id)',
],
columns: [
_column_94,
_column_50,
_column_95,
_column_101,
_column_97,
_column_98,
_column_99,
_column_37,
_column_93,
],
attachedDatabase: database,
),
alias: null);
}
i1.GeneratedColumn<int> _column_101(String aliasedName) =>
i1.GeneratedColumn<int>('affected_contact_id', aliasedName, true,
type: i1.DriftSqlType.int);
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
@ -1582,6 +1963,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema);
await from2To3(migrator, schema);
return 3;
case 3:
final schema = Schema4(database: database);
final migrator = i1.Migrator(database, schema);
await from3To4(migrator, schema);
return 4;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@ -1591,9 +1977,11 @@ i0.MigrationStepWithVersion migrationSteps({
i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
}) =>
i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
from1To2: from1To2,
from2To3: from2To3,
from3To4: from3To4,
));

View file

@ -352,9 +352,8 @@ class ApiService {
if (contact != null) {
await twonlyDB.contactsDao.updateContact(
contactId,
ContactsCompanion(
accountDeleted: const Value(true),
username: Value('${contact.username} (${contact.userId})'),
const ContactsCompanion(
accountDeleted: Value(true),
),
);
}
@ -496,7 +495,8 @@ class ApiService {
}
Future<void> checkForDeletedUsernames() async {
final users = await twonlyDB.contactsDao.getContactsByUsername('[deleted]');
final users = await twonlyDB.contactsDao
.getContactsByUsername('[deleted]', username2: '[Unknown]');
for (final user in users) {
final userData = await getUserById(user.userId);
if (userData != null) {

View file

@ -117,6 +117,10 @@ Future<void> handleGroupUpdate(
final group = (await twonlyDB.groupsDao.getGroup(groupId))!;
if (!group.isDirectChat) {
unawaited(fetchGroupState(group));
}
switch (actionType) {
case GroupActionType.updatedGroupName:
await twonlyDB.groupsDao.insertGroupAction(
@ -173,10 +177,6 @@ Future<void> handleGroupUpdate(
case GroupActionType.createdGroup:
break;
}
if (!group.isDirectChat) {
unawaited(fetchGroupState(group));
}
}
Future<bool> handleGroupJoin(

View file

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:hashlib/random.dart';
import 'package:mutex/mutex.dart';
@ -118,13 +119,17 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
.getContactByUserId(fromUserId)
.getSingleOrNull() ==
null) {
final user = await apiService.getUserById(fromUserId);
/// In case the user does not exists, just create a dummy user which was deleted by the user, so the message
/// can be inserted into the receipts database
await twonlyDB.contactsDao.insertContact(
ContactsCompanion(
userId: Value(fromUserId),
deletedByUser: const Value(true),
username: const Value('[deleted]'),
username: Value(
user == null ? '[Unknown]' : utf8.decode(user.username),
),
),
);
}

View file

@ -820,15 +820,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
...widget.mainCameraController.scannedNewProfiles.values
.map(
(c) {
if (c.isLoading) return Container();
return GestureDetector(
onTap: () async {
if (c.isLoading) return;
c.isLoading = true;
widget.mainCameraController.setState();
await addNewContactFromPublicProfile(c.profile);
widget.mainCameraController.scannedNewProfiles
.remove(c.profile.userId.toInt());
widget.mainCameraController.setState();
},
child: Container(
padding: const EdgeInsets.all(12),

View file

@ -5,6 +5,7 @@ import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/flame.service.dart';
import 'package:twonly/src/services/subscription.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/components/animate_icon.dart';
import 'package:twonly/src/views/components/better_list_title.dart';
@ -71,6 +72,9 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
);
return;
}
Log.info(
'Restoring flames from ${_directChat!.flameCounter} to ${_directChat!.maxFlameCounter}',
);
await twonlyDB.groupsDao.updateGroup(
_groupId,
GroupsCompanion(

View file

@ -53,19 +53,19 @@ class OnboardingView extends StatelessWidget {
),
),
),
PageViewModel(
title: context.lang.onboardingSendTwonliesTitle,
body: context.lang.onboardingSendTwonliesBody,
image: Center(
child: Padding(
padding: const EdgeInsets.only(top: 100),
child: Lottie.asset(
'assets/animations/twonlies.json',
repeat: false,
),
),
),
),
// PageViewModel(
// title: context.lang.onboardingSendTwonliesTitle,
// body: context.lang.onboardingSendTwonliesBody,
// image: Center(
// child: Padding(
// padding: const EdgeInsets.only(top: 100),
// child: Lottie.asset(
// 'assets/animations/twonlies.json',
// repeat: false,
// ),
// ),
// ),
// ),
PageViewModel(
title: context.lang.onboardingNotProductTitle,
bodyWidget: Column(

View file

@ -5,6 +5,7 @@ import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/components/avatar_icon.component.dart';
import 'package:twonly/src/views/components/better_list_title.dart';
import 'package:twonly/src/views/public_profile.view.dart';
import 'package:twonly/src/views/settings/account.view.dart';
import 'package:twonly/src/views/settings/appearance.view.dart';
import 'package:twonly/src/views/settings/backup/backup.view.dart';
@ -82,13 +83,22 @@ class _SettingsMainViewState extends State<SettingsMainView> {
),
),
),
// Align(
// alignment: Alignment.centerRight,
// child: IconButton(
// onPressed: () {},
// icon: FaIcon(FontAwesomeIcons.qrcode),
// ),
// )
Align(
alignment: Alignment.centerRight,
child: IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return const PublicProfileView();
},
),
);
},
icon: const FaIcon(FontAwesomeIcons.qrcode),
),
)
],
),
),

View file

@ -6,6 +6,7 @@ import 'package:drift/internal/migrations.dart';
import 'schema_v1.dart' as v1;
import 'schema_v2.dart' as v2;
import 'schema_v3.dart' as v3;
import 'schema_v4.dart' as v4;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
@ -17,10 +18,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v2.DatabaseAtV2(db);
case 3:
return v3.DatabaseAtV3(db);
case 4:
return v4.DatabaseAtV4(db);
default:
throw MissingSchemaException(version, versions);
}
}
static const versions = const [1, 2, 3];
static const versions = const [1, 2, 3, 4];
}

File diff suppressed because it is too large Load diff