mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 16:48:41 +00:00
add flame counter test
Some checks failed
Flutter analyze & test / flutter_analyze_and_test (push) Has been cancelled
Some checks failed
Flutter analyze & test / flutter_analyze_and_test (push) Has been cancelled
This commit is contained in:
parent
ad0ef841cc
commit
585f577f89
42 changed files with 407 additions and 247 deletions
|
|
@ -99,7 +99,7 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
.watchSingleOrNull();
|
.watchSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Contact>> getAllNotBlockedContacts() {
|
Future<List<Contact>> getAllContacts() {
|
||||||
return select(contacts).get();
|
return select(contacts).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:hashlib/random.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/services/flame.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
|
@ -278,89 +279,6 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
return query.map((row) => row.readTable(groups)).getSingleOrNull();
|
return query.map((row) => row.readTable(groups)).getSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> incFlameCounter(
|
|
||||||
String groupId,
|
|
||||||
bool received,
|
|
||||||
DateTime timestamp,
|
|
||||||
) async {
|
|
||||||
final group = await (select(groups)
|
|
||||||
..where((t) => t.groupId.equals(groupId)))
|
|
||||||
.getSingle();
|
|
||||||
|
|
||||||
final totalMediaCounter = group.totalMediaCounter + 1;
|
|
||||||
var flameCounter = group.flameCounter;
|
|
||||||
var maxFlameCounter = group.maxFlameCounter;
|
|
||||||
var maxFlameCounterFrom = group.maxFlameCounterFrom;
|
|
||||||
|
|
||||||
if (group.lastMessageReceived != null && group.lastMessageSend != null) {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final startOfToday = DateTime(now.year, now.month, now.day);
|
|
||||||
final twoDaysAgo = startOfToday.subtract(const Duration(days: 2));
|
|
||||||
if (group.lastMessageSend!.isBefore(twoDaysAgo) ||
|
|
||||||
group.lastMessageReceived!.isBefore(twoDaysAgo)) {
|
|
||||||
flameCounter = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastMessageSend = const Value<DateTime?>.absent();
|
|
||||||
var lastMessageReceived = const Value<DateTime?>.absent();
|
|
||||||
var lastFlameCounterChange = const Value<DateTime?>.absent();
|
|
||||||
|
|
||||||
if (group.lastFlameCounterChange != null) {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final startOfToday = DateTime(now.year, now.month, now.day);
|
|
||||||
|
|
||||||
if (group.lastFlameCounterChange!.isBefore(startOfToday)) {
|
|
||||||
// last flame update was yesterday. check if it can be updated.
|
|
||||||
var updateFlame = false;
|
|
||||||
if (received) {
|
|
||||||
if (group.lastMessageSend != null &&
|
|
||||||
group.lastMessageSend!.isAfter(startOfToday)) {
|
|
||||||
// today a message was already send -> update flame
|
|
||||||
updateFlame = true;
|
|
||||||
}
|
|
||||||
} else if (group.lastMessageReceived != null &&
|
|
||||||
group.lastMessageReceived!.isAfter(startOfToday)) {
|
|
||||||
// today a message was already received -> update flame
|
|
||||||
updateFlame = true;
|
|
||||||
}
|
|
||||||
if (updateFlame) {
|
|
||||||
flameCounter += 1;
|
|
||||||
lastFlameCounterChange = Value(timestamp);
|
|
||||||
// Overwrite max flame counter either the current is bigger or the th max flame counter is older then 4 days
|
|
||||||
if (flameCounter >= maxFlameCounter ||
|
|
||||||
maxFlameCounterFrom == null ||
|
|
||||||
maxFlameCounterFrom
|
|
||||||
.isBefore(DateTime.now().subtract(const Duration(days: 5)))) {
|
|
||||||
maxFlameCounter = flameCounter;
|
|
||||||
maxFlameCounterFrom = DateTime.now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// There where no message until no...
|
|
||||||
lastFlameCounterChange = Value(timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (received) {
|
|
||||||
lastMessageReceived = Value(timestamp);
|
|
||||||
} else {
|
|
||||||
lastMessageSend = Value(timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
await (update(groups)..where((t) => t.groupId.equals(groupId))).write(
|
|
||||||
GroupsCompanion(
|
|
||||||
totalMediaCounter: Value(totalMediaCounter),
|
|
||||||
lastFlameCounterChange: lastFlameCounterChange,
|
|
||||||
lastMessageReceived: lastMessageReceived,
|
|
||||||
lastMessageSend: lastMessageSend,
|
|
||||||
flameCounter: Value(flameCounter),
|
|
||||||
maxFlameCounter: Value(maxFlameCounter),
|
|
||||||
maxFlameCounterFrom: Value(maxFlameCounterFrom),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<int> watchSumTotalMediaCounter() {
|
Stream<int> watchSumTotalMediaCounter() {
|
||||||
final query = selectOnly(groups)
|
final query = selectOnly(groups)
|
||||||
..addColumns([groups.totalMediaCounter.sum()]);
|
..addColumns([groups.totalMediaCounter.sum()]);
|
||||||
|
|
@ -383,23 +301,3 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
.write(GroupsCompanion(lastMessageExchange: Value(newLastMessage)));
|
.write(GroupsCompanion(lastMessageExchange: Value(newLastMessage)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int getFlameCounterFromGroup(Group? group) {
|
|
||||||
if (group == null) return 0;
|
|
||||||
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.lastFlameCounterChange!.isAfter(oneDayAgo)) {
|
|
||||||
return group.flameCounter + 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:hashlib/random.dart';
|
import 'package:hashlib/random.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
|
@ -110,7 +111,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
final allGroups = await select(groups).get();
|
final allGroups = await select(groups).get();
|
||||||
|
|
||||||
for (final group in allGroups) {
|
for (final group in allGroups) {
|
||||||
final deletionTime = DateTime.now().subtract(
|
final deletionTime = clock.now().subtract(
|
||||||
Duration(
|
Duration(
|
||||||
milliseconds: group.deleteMessagesAfterMilliseconds,
|
milliseconds: group.deleteMessagesAfterMilliseconds,
|
||||||
),
|
),
|
||||||
|
|
@ -150,7 +151,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
// t.messageOtherId.isNull() &
|
// t.messageOtherId.isNull() &
|
||||||
// t.errorWhileSending.equals(false) &
|
// t.errorWhileSending.equals(false) &
|
||||||
// t.sendAt.isBiggerThanValue(
|
// t.sendAt.isBiggerThanValue(
|
||||||
// DateTime.now().subtract(const Duration(minutes: 10)),
|
// clock.now().subtract(const Duration(minutes: 10)),
|
||||||
// ),
|
// ),
|
||||||
// ))
|
// ))
|
||||||
// .get();
|
// .get();
|
||||||
|
|
@ -178,7 +179,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
Future<void> openedAllTextMessages(String groupId) {
|
Future<void> openedAllTextMessages(String groupId) {
|
||||||
final updates = MessagesCompanion(openedAt: Value(DateTime.now()));
|
final updates = MessagesCompanion(openedAt: Value(clock.now()));
|
||||||
return (update(messages)
|
return (update(messages)
|
||||||
..where(
|
..where(
|
||||||
(t) =>
|
(t) =>
|
||||||
|
|
@ -272,12 +273,12 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
// Directly show as message opened as soon as one person has opened it
|
// Directly show as message opened as soon as one person has opened it
|
||||||
final openedByAll =
|
final openedByAll =
|
||||||
await haveAllMembers(messageId, MessageActionType.openedAt)
|
await haveAllMembers(messageId, MessageActionType.openedAt)
|
||||||
? DateTime.now()
|
? clock.now()
|
||||||
: null;
|
: null;
|
||||||
await twonlyDB.messagesDao.updateMessageId(
|
await twonlyDB.messagesDao.updateMessageId(
|
||||||
messageId,
|
messageId,
|
||||||
MessagesCompanion(
|
MessagesCompanion(
|
||||||
openedAt: Value(DateTime.now()),
|
openedAt: Value(clock.now()),
|
||||||
openedByAll: Value(openedByAll),
|
openedByAll: Value(openedByAll),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -298,7 +299,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
);
|
);
|
||||||
await twonlyDB.messagesDao.updateMessageId(
|
await twonlyDB.messagesDao.updateMessageId(
|
||||||
messageId,
|
messageId,
|
||||||
MessagesCompanion(ackByServer: Value(DateTime.now())),
|
MessagesCompanion(ackByServer: Value(clock.now())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -378,7 +379,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
await twonlyDB.groupsDao.updateGroup(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
message.groupId.value,
|
message.groupId.value,
|
||||||
GroupsCompanion(
|
GroupsCompanion(
|
||||||
lastMessageExchange: Value(DateTime.now()),
|
lastMessageExchange: Value(clock.now()),
|
||||||
archived: const Value(false),
|
archived: const Value(false),
|
||||||
deletedContent: const Value(false),
|
deletedContent: const Value(false),
|
||||||
),
|
),
|
||||||
|
|
@ -389,7 +390,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
message.groupId.value,
|
message.groupId.value,
|
||||||
message.senderId.value!,
|
message.senderId.value!,
|
||||||
GroupMembersCompanion(
|
GroupMembersCompanion(
|
||||||
lastMessage: Value(DateTime.now()),
|
lastMessage: Value(clock.now()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:hashlib/random.dart';
|
import 'package:hashlib/random.dart';
|
||||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||||
|
|
@ -81,7 +82,7 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Receipt>> getReceiptsForRetransmission() async {
|
Future<List<Receipt>> getReceiptsForRetransmission() async {
|
||||||
final markedRetriesTime = DateTime.now().subtract(
|
final markedRetriesTime = clock.now().subtract(
|
||||||
const Duration(
|
const Duration(
|
||||||
// give the server time to transmit all messages to the client
|
// give the server time to transmit all messages to the client
|
||||||
seconds: 20,
|
seconds: 20,
|
||||||
|
|
@ -111,7 +112,7 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
Future<void> markMessagesForRetry(int contactId) async {
|
Future<void> markMessagesForRetry(int contactId) async {
|
||||||
await (update(receipts)..where((c) => c.contactId.equals(contactId))).write(
|
await (update(receipts)..where((c) => c.contactId.equals(contactId))).write(
|
||||||
ReceiptsCompanion(
|
ReceiptsCompanion(
|
||||||
markForRetry: Value(DateTime.now()),
|
markForRetry: Value(clock.now()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/signal_contact_prekey.table.dart';
|
import 'package:twonly/src/database/tables/signal_contact_prekey.table.dart';
|
||||||
|
|
@ -107,7 +108,7 @@ class SignalDao extends DatabaseAccessor<TwonlyDB> with _$SignalDaoMixin {
|
||||||
await (delete(signalContactPreKeys)
|
await (delete(signalContactPreKeys)
|
||||||
..where(
|
..where(
|
||||||
(t) => (t.createdAt.isSmallerThanValue(
|
(t) => (t.createdAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(
|
clock.now().subtract(
|
||||||
const Duration(days: 100),
|
const Duration(days: 100),
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
|
@ -117,7 +118,7 @@ class SignalDao extends DatabaseAccessor<TwonlyDB> with _$SignalDaoMixin {
|
||||||
await (delete(twonlyDB.signalPreKeyStores)
|
await (delete(twonlyDB.signalPreKeyStores)
|
||||||
..where(
|
..where(
|
||||||
(t) => (t.createdAt.isSmallerThanValue(
|
(t) => (t.createdAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(
|
clock.now().subtract(
|
||||||
const Duration(days: 365),
|
const Duration(days: 365),
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift_flutter/drift_flutter.dart'
|
import 'package:drift_flutter/drift_flutter.dart'
|
||||||
show DriftNativeOptions, driftDatabase;
|
show DriftNativeOptions, driftDatabase;
|
||||||
|
|
@ -160,7 +161,7 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
await (delete(signalPreKeyStores)
|
await (delete(signalPreKeyStores)
|
||||||
..where(
|
..where(
|
||||||
(t) => (t.createdAt.isSmallerThanValue(
|
(t) => (t.createdAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(
|
clock.now().subtract(
|
||||||
const Duration(days: 25),
|
const Duration(days: 25),
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift_flutter/drift_flutter.dart'
|
import 'package:drift_flutter/drift_flutter.dart'
|
||||||
show DriftNativeOptions, driftDatabase;
|
show DriftNativeOptions, driftDatabase;
|
||||||
|
|
@ -191,7 +192,7 @@ class TwonlyDatabaseOld extends _$TwonlyDatabaseOld {
|
||||||
await (delete(signalPreKeyStores)
|
await (delete(signalPreKeyStores)
|
||||||
..where(
|
..where(
|
||||||
(t) => (t.createdAt.isSmallerThanValue(
|
(t) => (t.createdAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(
|
clock.now().subtract(
|
||||||
const Duration(days: 25),
|
const Duration(days: 25),
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
|
|
@ -206,7 +207,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
|
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
|
||||||
final startTime = DateTime.now();
|
final startTime = clock.now();
|
||||||
|
|
||||||
const timeout = Duration(seconds: 60);
|
const timeout = Duration(seconds: 60);
|
||||||
|
|
||||||
|
|
@ -216,7 +217,7 @@ class ApiService {
|
||||||
messagesV0.remove(seq);
|
messagesV0.remove(seq);
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
if (DateTime.now().difference(startTime) > timeout) {
|
if (clock.now().difference(startTime) > timeout) {
|
||||||
Log.warn('Timeout for message $seq');
|
Log.warn('Timeout for message $seq');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||||
import 'package:twonly/src/services/api/utils.dart';
|
import 'package:twonly/src/services/api/utils.dart';
|
||||||
|
import 'package:twonly/src/services/flame.service.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
|
|
@ -114,7 +115,7 @@ Future<void> handleMedia(
|
||||||
await twonlyDB.groupsDao
|
await twonlyDB.groupsDao
|
||||||
.increaseLastMessageExchange(groupId, fromTimestamp(media.timestamp));
|
.increaseLastMessageExchange(groupId, fromTimestamp(media.timestamp));
|
||||||
Log.info('Inserted a new media message with ID: ${message.messageId}');
|
Log.info('Inserted a new media message with ID: ${message.messageId}');
|
||||||
await twonlyDB.groupsDao.incFlameCounter(
|
await incFlameCounter(
|
||||||
message.groupId,
|
message.groupId,
|
||||||
true,
|
true,
|
||||||
fromTimestamp(media.timestamp),
|
fromTimestamp(media.timestamp),
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
DateTime lastPushKeyRequest = DateTime.now().subtract(const Duration(hours: 1));
|
DateTime lastPushKeyRequest = clock.now().subtract(const Duration(hours: 1));
|
||||||
|
|
||||||
Future<void> handlePushKey(
|
Future<void> handlePushKey(
|
||||||
int contactId,
|
int contactId,
|
||||||
|
|
@ -14,8 +15,8 @@ Future<void> handlePushKey(
|
||||||
case EncryptedContent_PushKeys_Type.REQUEST:
|
case EncryptedContent_PushKeys_Type.REQUEST:
|
||||||
Log.info('Got a pushkey request from $contactId');
|
Log.info('Got a pushkey request from $contactId');
|
||||||
if (lastPushKeyRequest
|
if (lastPushKeyRequest
|
||||||
.isBefore(DateTime.now().subtract(const Duration(seconds: 60)))) {
|
.isBefore(clock.now().subtract(const Duration(seconds: 60)))) {
|
||||||
lastPushKeyRequest = DateTime.now();
|
lastPushKeyRequest = clock.now();
|
||||||
unawaited(setupNotificationWithUsers(forceContact: contactId));
|
unawaited(setupNotificationWithUsers(forceContact: contactId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
@ -17,7 +18,6 @@ Future<void> handleReaction(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!reaction.remove) {
|
if (!reaction.remove) {
|
||||||
await twonlyDB.groupsDao
|
await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now());
|
||||||
.increaseLastMessageExchange(groupId, DateTime.now());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||||
|
|
@ -26,7 +27,7 @@ Future<void> handleTextMessage(
|
||||||
textMessage.hasQuoteMessageId() ? textMessage.quoteMessageId : null,
|
textMessage.hasQuoteMessageId() ? textMessage.quoteMessageId : null,
|
||||||
),
|
),
|
||||||
createdAt: Value(fromTimestamp(textMessage.timestamp)),
|
createdAt: Value(fromTimestamp(textMessage.timestamp)),
|
||||||
ackByServer: Value(DateTime.now()),
|
ackByServer: Value(clock.now()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await twonlyDB.groupsDao.increaseLastMessageExchange(
|
await twonlyDB.groupsDao.increaseLastMessageExchange(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
|
@ -92,7 +93,7 @@ Future<void> handleUploadStatusUpdate(TaskStatusUpdate update) async {
|
||||||
await twonlyDB.messagesDao.handleMessageAckByServer(
|
await twonlyDB.messagesDao.handleMessageAckByServer(
|
||||||
contact.contactId,
|
contact.contactId,
|
||||||
message.messageId,
|
message.messageId,
|
||||||
DateTime.now(),
|
clock.now(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
|
|
@ -18,6 +19,7 @@ import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/media_background.service.dart';
|
import 'package:twonly/src/services/api/mediafiles/media_background.service.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
|
import 'package:twonly/src/services/flame.service.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
@ -101,8 +103,7 @@ Future<void> insertMediaFileInMessagesTable(
|
||||||
type: const Value(MessageType.media),
|
type: const Value(MessageType.media),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await twonlyDB.groupsDao
|
await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now());
|
||||||
.increaseLastMessageExchange(groupId, DateTime.now());
|
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
// de-archive contact when sending a new message
|
// de-archive contact when sending a new message
|
||||||
await twonlyDB.groupsDao.updateGroup(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
|
|
@ -207,11 +208,7 @@ Future<void> _createUploadRequest(MediaFileService media) async {
|
||||||
await twonlyDB.groupsDao.getGroupNonLeftMembers(message.groupId);
|
await twonlyDB.groupsDao.getGroupNonLeftMembers(message.groupId);
|
||||||
|
|
||||||
if (media.mediaFile.reuploadRequestedBy == null) {
|
if (media.mediaFile.reuploadRequestedBy == null) {
|
||||||
await twonlyDB.groupsDao.incFlameCounter(
|
await incFlameCounter(message.groupId, false, message.createdAt);
|
||||||
message.groupId,
|
|
||||||
false,
|
|
||||||
message.createdAt,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final groupMember in groupMembers) {
|
for (final groupMember in groupMembers) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
|
|
@ -131,7 +132,7 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
await twonlyDB.messagesDao.handleMessageAckByServer(
|
await twonlyDB.messagesDao.handleMessageAckByServer(
|
||||||
receipt.contactId,
|
receipt.contactId,
|
||||||
receipt.messageId!,
|
receipt.messageId!,
|
||||||
DateTime.now(),
|
clock.now(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!receipt.contactWillSendsReceipt) {
|
if (!receipt.contactWillSendsReceipt) {
|
||||||
|
|
@ -140,9 +141,9 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
await twonlyDB.receiptsDao.updateReceipt(
|
await twonlyDB.receiptsDao.updateReceipt(
|
||||||
receiptId,
|
receiptId,
|
||||||
ReceiptsCompanion(
|
ReceiptsCompanion(
|
||||||
ackByServerAt: Value(DateTime.now()),
|
ackByServerAt: Value(clock.now()),
|
||||||
retryCount: Value(receipt.retryCount + 1),
|
retryCount: Value(receipt.retryCount + 1),
|
||||||
lastRetry: Value(DateTime.now()),
|
lastRetry: Value(clock.now()),
|
||||||
markForRetry: const Value(null),
|
markForRetry: const Value(null),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -210,7 +211,7 @@ Future<void> sendCipherTextToGroup(
|
||||||
}) async {
|
}) async {
|
||||||
final groupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers(groupId);
|
final groupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers(groupId);
|
||||||
|
|
||||||
await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, DateTime.now());
|
await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now());
|
||||||
|
|
||||||
encryptedContent.groupId = groupId;
|
encryptedContent.groupId = groupId;
|
||||||
|
|
||||||
|
|
@ -242,7 +243,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
||||||
contactId: Value(contactId),
|
contactId: Value(contactId),
|
||||||
message: Value(response.writeToBuffer()),
|
message: Value(response.writeToBuffer()),
|
||||||
messageId: Value(messageId),
|
messageId: Value(messageId),
|
||||||
ackByServerAt: Value(onlyReturnEncryptedData ? DateTime.now() : null),
|
ackByServerAt: Value(onlyReturnEncryptedData ? clock.now() : null),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -268,7 +269,7 @@ Future<void> notifyContactAboutOpeningMessage(
|
||||||
}
|
}
|
||||||
Log.info('Opened messages: $messageOtherIds');
|
Log.info('Opened messages: $messageOtherIds');
|
||||||
|
|
||||||
final actionAt = DateTime.now();
|
final actionAt = clock.now();
|
||||||
|
|
||||||
await sendCipherText(
|
await sendCipherText(
|
||||||
contactId,
|
contactId,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:hashlib/random.dart';
|
import 'package:hashlib/random.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
|
|
@ -60,7 +61,7 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
|
||||||
await apiService.sendResponse(ClientToServer()..v0 = v0);
|
await apiService.sendResponse(ClientToServer()..v0 = v0);
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime lastPushKeyRequest = DateTime.now().subtract(const Duration(hours: 1));
|
DateTime lastPushKeyRequest = clock.now().subtract(const Duration(hours: 1));
|
||||||
|
|
||||||
Mutex protectReceiptCheck = Mutex();
|
Mutex protectReceiptCheck = Mutex();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/groups.dao.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
|
|
@ -32,10 +32,10 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final flameCounter = getFlameCounterFromGroup(group) - 1;
|
final flameCounter = getFlameCounterFromGroup(group);
|
||||||
|
|
||||||
// only sync when flame counter is higher than three days or when they are bestFriends
|
// only sync when flame counter is higher three or when they are bestFriends
|
||||||
if (flameCounter < 1 && bestFriend.groupId != group.groupId) continue;
|
if (flameCounter <= 2 && bestFriend.groupId != group.groupId) continue;
|
||||||
|
|
||||||
final groupMembers =
|
final groupMembers =
|
||||||
await twonlyDB.groupsDao.getGroupNonLeftMembers(group.groupId);
|
await twonlyDB.groupsDao.getGroupNonLeftMembers(group.groupId);
|
||||||
|
|
@ -59,8 +59,125 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
|
||||||
await twonlyDB.groupsDao.updateGroup(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
group.groupId,
|
group.groupId,
|
||||||
GroupsCompanion(
|
GroupsCompanion(
|
||||||
lastFlameSync: Value(DateTime.now()),
|
lastFlameSync: Value(clock.now()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getFlameCounterFromGroup(Group? group) {
|
||||||
|
if (group == null) return 0;
|
||||||
|
if (group.lastMessageSend == null ||
|
||||||
|
group.lastMessageReceived == null ||
|
||||||
|
group.lastFlameCounterChange == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
final now = clock.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.lastFlameCounterChange!.isAfter(oneDayAgo)) {
|
||||||
|
return group.flameCounter;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> incFlameCounter(
|
||||||
|
String groupId,
|
||||||
|
bool received,
|
||||||
|
DateTime timestamp,
|
||||||
|
) async {
|
||||||
|
final group = await twonlyDB.groupsDao.getGroup(groupId);
|
||||||
|
if (group == null) return;
|
||||||
|
|
||||||
|
final totalMediaCounter = group.totalMediaCounter + 1;
|
||||||
|
var flameCounter = group.flameCounter;
|
||||||
|
var maxFlameCounter = group.maxFlameCounter;
|
||||||
|
var maxFlameCounterFrom = group.maxFlameCounterFrom;
|
||||||
|
|
||||||
|
if (group.lastMessageReceived != null && group.lastMessageSend != null) {
|
||||||
|
final now = clock.now();
|
||||||
|
final startOfToday = DateTime(now.year, now.month, now.day);
|
||||||
|
final twoDaysAgo = startOfToday.subtract(const Duration(days: 2));
|
||||||
|
if (group.lastMessageSend!.isBefore(twoDaysAgo) ||
|
||||||
|
group.lastMessageReceived!.isBefore(twoDaysAgo)) {
|
||||||
|
flameCounter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastMessageSend = const Value<DateTime?>.absent();
|
||||||
|
var lastMessageReceived = const Value<DateTime?>.absent();
|
||||||
|
var lastFlameCounterChange = const Value<DateTime?>.absent();
|
||||||
|
|
||||||
|
final now = clock.now();
|
||||||
|
final startOfToday = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
|
if (group.lastFlameCounterChange == null ||
|
||||||
|
group.lastFlameCounterChange!.isBefore(startOfToday)) {
|
||||||
|
// last flame update was yesterday. check if it can be updated.
|
||||||
|
var updateFlame = false;
|
||||||
|
if (received) {
|
||||||
|
if (group.lastMessageSend != null &&
|
||||||
|
group.lastMessageSend!.isAfter(startOfToday)) {
|
||||||
|
// today a message was already send -> update flame
|
||||||
|
updateFlame = true;
|
||||||
|
}
|
||||||
|
} else if (group.lastMessageReceived != null &&
|
||||||
|
group.lastMessageReceived!.isAfter(startOfToday)) {
|
||||||
|
// today a message was already received -> update flame
|
||||||
|
updateFlame = true;
|
||||||
|
}
|
||||||
|
if (updateFlame) {
|
||||||
|
flameCounter += 1;
|
||||||
|
if (group.lastFlameCounterChange == null ||
|
||||||
|
group.lastFlameCounterChange!.isBefore(timestamp)) {
|
||||||
|
// only update if the timestamp is newer
|
||||||
|
lastFlameCounterChange = Value(timestamp);
|
||||||
|
}
|
||||||
|
// Overwrite max flame counter either the current is bigger or the the max flame counter is older then 4 days
|
||||||
|
if (flameCounter >= maxFlameCounter ||
|
||||||
|
maxFlameCounterFrom == null ||
|
||||||
|
maxFlameCounterFrom
|
||||||
|
.isBefore(clock.now().subtract(const Duration(days: 5)))) {
|
||||||
|
maxFlameCounter = flameCounter;
|
||||||
|
maxFlameCounterFrom = clock.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (received) {
|
||||||
|
if (group.lastMessageReceived == null ||
|
||||||
|
group.lastMessageReceived!.isBefore(timestamp)) {
|
||||||
|
lastMessageReceived = Value(timestamp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (group.lastMessageSend == null ||
|
||||||
|
group.lastMessageSend!.isBefore(timestamp)) {
|
||||||
|
lastMessageSend = Value(timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
|
groupId,
|
||||||
|
GroupsCompanion(
|
||||||
|
totalMediaCounter: Value(totalMediaCounter),
|
||||||
|
lastFlameCounterChange: lastFlameCounterChange,
|
||||||
|
lastMessageReceived: lastMessageReceived,
|
||||||
|
lastMessageSend: lastMessageSend,
|
||||||
|
flameCounter: Value(flameCounter),
|
||||||
|
maxFlameCounter: Value(maxFlameCounter),
|
||||||
|
maxFlameCounterFrom: Value(maxFlameCounterFrom),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isItPossibleToRestoreFlames(Group group) {
|
||||||
|
final flameCounter = getFlameCounterFromGroup(group);
|
||||||
|
return group.maxFlameCounter > 2 &&
|
||||||
|
flameCounter < group.maxFlameCounter &&
|
||||||
|
group.maxFlameCounterFrom!
|
||||||
|
.isAfter(clock.now().subtract(const Duration(days: 5)));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
|
|
@ -158,7 +159,7 @@ Future<void> fetchMissingGroupPublicKey() async {
|
||||||
if (member.lastMessage == null) continue;
|
if (member.lastMessage == null) continue;
|
||||||
// only request if the users has send a message in the last two days.
|
// only request if the users has send a message in the last two days.
|
||||||
if (member.lastMessage!
|
if (member.lastMessage!
|
||||||
.isAfter(DateTime.now().subtract(const Duration(days: 2)))) {
|
.isAfter(clock.now().subtract(const Duration(days: 2)))) {
|
||||||
await sendCipherText(
|
await sendCipherText(
|
||||||
member.contactId,
|
member.contactId,
|
||||||
EncryptedContent(
|
EncryptedContent(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
|
@ -61,7 +62,7 @@ class MediaFileService {
|
||||||
// delete = true; // do not overwrite a previous delete = false
|
// delete = true; // do not overwrite a previous delete = false
|
||||||
// this is just to make it easier to understand :)
|
// this is just to make it easier to understand :)
|
||||||
} else if (message.openedAt!
|
} else if (message.openedAt!
|
||||||
.isAfter(DateTime.now().subtract(const Duration(days: 2)))) {
|
.isAfter(clock.now().subtract(const Duration(days: 2)))) {
|
||||||
// In case the image was opened, but send with unlimited time or no authentication.
|
// In case the image was opened, but send with unlimited time or no authentication.
|
||||||
if (message.senderId == null) {
|
if (message.senderId == null) {
|
||||||
delete = false;
|
delete = false;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
|
|
@ -46,7 +47,7 @@ Future<void> setupNotificationWithUsers({
|
||||||
|
|
||||||
final random = Random.secure();
|
final random = Random.secure();
|
||||||
|
|
||||||
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||||
for (final contact in contacts) {
|
for (final contact in contacts) {
|
||||||
final pushUser =
|
final pushUser =
|
||||||
pushUsers.firstWhereOrNull((x) => x.userId == contact.userId);
|
pushUsers.firstWhereOrNull((x) => x.userId == contact.userId);
|
||||||
|
|
@ -54,7 +55,7 @@ Future<void> setupNotificationWithUsers({
|
||||||
if (pushUser != null && pushUser.pushKeys.isNotEmpty) {
|
if (pushUser != null && pushUser.pushKeys.isNotEmpty) {
|
||||||
// make it harder to predict the change of the key
|
// make it harder to predict the change of the key
|
||||||
final timeBefore =
|
final timeBefore =
|
||||||
DateTime.now().subtract(Duration(days: 5 + random.nextInt(5)));
|
clock.now().subtract(Duration(days: 10 + random.nextInt(5)));
|
||||||
final lastKey = pushUser.pushKeys.last;
|
final lastKey = pushUser.pushKeys.last;
|
||||||
final createdAt = DateTime.fromMillisecondsSinceEpoch(
|
final createdAt = DateTime.fromMillisecondsSinceEpoch(
|
||||||
lastKey.createdAtUnixTimestamp.toInt(),
|
lastKey.createdAtUnixTimestamp.toInt(),
|
||||||
|
|
@ -66,7 +67,7 @@ Future<void> setupNotificationWithUsers({
|
||||||
final pushKey = PushKey(
|
final pushKey = PushKey(
|
||||||
id: lastKey.id + random.nextInt(5),
|
id: lastKey.id + random.nextInt(5),
|
||||||
key: List<int>.generate(32, (index) => random.nextInt(256)),
|
key: List<int>.generate(32, (index) => random.nextInt(256)),
|
||||||
createdAtUnixTimestamp: Int64(DateTime.now().millisecondsSinceEpoch),
|
createdAtUnixTimestamp: Int64(clock.now().millisecondsSinceEpoch),
|
||||||
);
|
);
|
||||||
await sendNewPushKey(contact.userId, pushKey);
|
await sendNewPushKey(contact.userId, pushKey);
|
||||||
// only store a maximum of two keys
|
// only store a maximum of two keys
|
||||||
|
|
@ -86,7 +87,7 @@ Future<void> setupNotificationWithUsers({
|
||||||
final pushKey = PushKey(
|
final pushKey = PushKey(
|
||||||
id: Int64(1),
|
id: Int64(1),
|
||||||
key: List<int>.generate(32, (index) => random.nextInt(256)),
|
key: List<int>.generate(32, (index) => random.nextInt(256)),
|
||||||
createdAtUnixTimestamp: Int64(DateTime.now().millisecondsSinceEpoch),
|
createdAtUnixTimestamp: Int64(clock.now().millisecondsSinceEpoch),
|
||||||
);
|
);
|
||||||
await sendNewPushKey(contact.userId, pushKey);
|
await sendNewPushKey(contact.userId, pushKey);
|
||||||
pushUsers.add(
|
pushUsers.add(
|
||||||
|
|
@ -173,7 +174,7 @@ Future<void> handleNewPushKey(int fromUserId, int keyId, List<int> key) async {
|
||||||
PushKey(
|
PushKey(
|
||||||
id: Int64(keyId),
|
id: Int64(keyId),
|
||||||
key: key,
|
key: key,
|
||||||
createdAtUnixTimestamp: Int64(DateTime.now().millisecondsSinceEpoch),
|
createdAtUnixTimestamp: Int64(clock.now().millisecondsSinceEpoch),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -341,7 +342,7 @@ Future<Uint8List?> encryptPushNotification(
|
||||||
final createdAt = DateTime.fromMillisecondsSinceEpoch(
|
final createdAt = DateTime.fromMillisecondsSinceEpoch(
|
||||||
pushUser.pushKeys.last.createdAtUnixTimestamp.toInt(),
|
pushUser.pushKeys.last.createdAtUnixTimestamp.toInt(),
|
||||||
);
|
);
|
||||||
final timeBefore = DateTime.now().subtract(const Duration(days: 8));
|
final timeBefore = clock.now().subtract(const Duration(days: 8));
|
||||||
if (createdAt.isBefore(timeBefore)) {
|
if (createdAt.isBefore(timeBefore)) {
|
||||||
await requestNewPushKeysForUser(toUserId);
|
await requestNewPushKeysForUser(toUserId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
|
@ -21,8 +21,7 @@ Future<IdentityKeyPair?> getSignalIdentityKeyPair() async {
|
||||||
// It then checks if it should update a new session key
|
// It then checks if it should update a new session key
|
||||||
Future<void> signalHandleNewServerConnection() async {
|
Future<void> signalHandleNewServerConnection() async {
|
||||||
if (gUser.signalLastSignedPreKeyUpdated != null) {
|
if (gUser.signalLastSignedPreKeyUpdated != null) {
|
||||||
final fortyEightHoursAgo =
|
final fortyEightHoursAgo = clock.now().subtract(const Duration(hours: 48));
|
||||||
DateTime.now().subtract(const Duration(hours: 48));
|
|
||||||
final isYoungerThan48Hours =
|
final isYoungerThan48Hours =
|
||||||
(gUser.signalLastSignedPreKeyUpdated!).isAfter(fortyEightHoursAgo);
|
(gUser.signalLastSignedPreKeyUpdated!).isAfter(fortyEightHoursAgo);
|
||||||
if (isYoungerThan48Hours) {
|
if (isYoungerThan48Hours) {
|
||||||
|
|
@ -36,7 +35,7 @@ Future<void> signalHandleNewServerConnection() async {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
user.signalLastSignedPreKeyUpdated = DateTime.now();
|
user.signalLastSignedPreKeyUpdated = clock.now();
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
final res = await apiService.updateSignedPreKey(
|
final res = await apiService.updateSignedPreKey(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
|
@ -22,17 +23,17 @@ class OtherPreKeys {
|
||||||
}
|
}
|
||||||
|
|
||||||
Mutex requestNewKeys = Mutex();
|
Mutex requestNewKeys = Mutex();
|
||||||
DateTime lastPreKeyRequest = DateTime.now().subtract(const Duration(hours: 1));
|
DateTime lastPreKeyRequest = clock.now().subtract(const Duration(hours: 1));
|
||||||
DateTime lastSignedPreKeyRequest =
|
DateTime lastSignedPreKeyRequest =
|
||||||
DateTime.now().subtract(const Duration(hours: 1));
|
clock.now().subtract(const Duration(hours: 1));
|
||||||
|
|
||||||
Future<void> requestNewPrekeysForContact(int contactId) async {
|
Future<void> requestNewPrekeysForContact(int contactId) async {
|
||||||
if (lastPreKeyRequest
|
if (lastPreKeyRequest
|
||||||
.isAfter(DateTime.now().subtract(const Duration(seconds: 60)))) {
|
.isAfter(clock.now().subtract(const Duration(seconds: 60)))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.info('[PREKEY] Requesting new PREKEYS for $contactId');
|
Log.info('[PREKEY] Requesting new PREKEYS for $contactId');
|
||||||
lastPreKeyRequest = DateTime.now();
|
lastPreKeyRequest = clock.now();
|
||||||
await requestNewKeys.protect(() async {
|
await requestNewKeys.protect(() async {
|
||||||
final otherKeys = await apiService.getPreKeysByUserId(contactId);
|
final otherKeys = await apiService.getPreKeysByUserId(contactId);
|
||||||
if (otherKeys != null) {
|
if (otherKeys != null) {
|
||||||
|
|
@ -65,11 +66,11 @@ Future<SignalContactPreKey?> getPreKeyByContactId(int contactId) async {
|
||||||
|
|
||||||
Future<void> requestNewSignedPreKeyForContact(int contactId) async {
|
Future<void> requestNewSignedPreKeyForContact(int contactId) async {
|
||||||
if (lastSignedPreKeyRequest
|
if (lastSignedPreKeyRequest
|
||||||
.isAfter(DateTime.now().subtract(const Duration(seconds: 60)))) {
|
.isAfter(clock.now().subtract(const Duration(seconds: 60)))) {
|
||||||
Log.info('last signed pre request was 60s before');
|
Log.info('last signed pre request was 60s before');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastSignedPreKeyRequest = DateTime.now();
|
lastSignedPreKeyRequest = clock.now();
|
||||||
await requestNewKeys.protect(() async {
|
await requestNewKeys.protect(() async {
|
||||||
final signedPreKey = await apiService.getSignedKeyByUserId(contactId);
|
final signedPreKey = await apiService.getSignedKeyByUserId(contactId);
|
||||||
if (signedPreKey != null) {
|
if (signedPreKey != null) {
|
||||||
|
|
@ -96,8 +97,7 @@ Future<SignalContactSignedPreKey?> getSignedPreKeyByContactId(
|
||||||
await twonlyDB.signalDao.getSignedPreKeyByContactId(contactId);
|
await twonlyDB.signalDao.getSignedPreKeyByContactId(contactId);
|
||||||
|
|
||||||
if (signedPreKey != null) {
|
if (signedPreKey != null) {
|
||||||
final fortyEightHoursAgo =
|
final fortyEightHoursAgo = clock.now().subtract(const Duration(hours: 48));
|
||||||
DateTime.now().subtract(const Duration(hours: 48));
|
|
||||||
final isOlderThan48Hours =
|
final isOlderThan48Hours =
|
||||||
signedPreKey.createdAt.isBefore(fortyEightHoursAgo);
|
signedPreKey.createdAt.isBefore(fortyEightHoursAgo);
|
||||||
if (isOlderThan48Hours) {
|
if (isOlderThan48Hours) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
@ -34,8 +35,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
|
||||||
|
|
||||||
final lastUpdateTime = gUser.twonlySafeBackup!.lastBackupDone;
|
final lastUpdateTime = gUser.twonlySafeBackup!.lastBackupDone;
|
||||||
if (!force && lastUpdateTime != null) {
|
if (!force && lastUpdateTime != null) {
|
||||||
if (lastUpdateTime
|
if (lastUpdateTime.isAfter(clock.now().subtract(const Duration(days: 1)))) {
|
||||||
.isAfter(DateTime.now().subtract(const Duration(days: 1)))) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +118,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
|
||||||
|
|
||||||
if (gUser.twonlySafeBackup!.lastBackupDone == null ||
|
if (gUser.twonlySafeBackup!.lastBackupDone == null ||
|
||||||
gUser.twonlySafeBackup!.lastBackupDone!
|
gUser.twonlySafeBackup!.lastBackupDone!
|
||||||
.isAfter(DateTime.now().subtract(const Duration(days: 90)))) {
|
.isAfter(clock.now().subtract(const Duration(days: 90)))) {
|
||||||
force = true;
|
force = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,7 +190,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
|
||||||
Log.info('Starting upload from twonly Backup.');
|
Log.info('Starting upload from twonly Backup.');
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.pending;
|
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.pending;
|
||||||
user.twonlySafeBackup!.lastBackupDone = DateTime.now();
|
user.twonlySafeBackup!.lastBackupDone = clock.now();
|
||||||
user.twonlySafeBackup!.lastBackupSize = encryptedBackupBytes.length;
|
user.twonlySafeBackup!.lastBackupSize = encryptedBackupBytes.length;
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
Future<void> createPushAvatars() async {
|
Future<void> createPushAvatars() async {
|
||||||
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||||
|
|
||||||
for (final contact in contacts) {
|
for (final contact in contacts) {
|
||||||
if (contact.avatarSvgCompressed == null) continue;
|
if (contact.avatarSvgCompressed == null) continue;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
|
|
@ -96,7 +97,7 @@ Future<void> _writeLogToFile(LogRecord record) async {
|
||||||
|
|
||||||
// Prepare the log message
|
// Prepare the log message
|
||||||
final logMessage =
|
final logMessage =
|
||||||
'${DateTime.now().toString().split(".")[0]} ${record.level.name} [twonly] ${record.loggerName} > ${record.message}\n';
|
'${clock.now().toString().split(".")[0]} ${record.level.name} [twonly] ${record.loggerName} > ${record.message}\n';
|
||||||
|
|
||||||
await writeToLogGuard.protect(() async {
|
await writeToLogGuard.protect(() async {
|
||||||
// Append the log message to the file
|
// Append the log message to the file
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:convert/convert.dart';
|
import 'package:convert/convert.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -197,7 +198,7 @@ bool isDarkMode(BuildContext context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isToday(DateTime lastImageSend) {
|
bool isToday(DateTime lastImageSend) {
|
||||||
final now = DateTime.now();
|
final now = clock.now();
|
||||||
return lastImageSend.year == now.year &&
|
return lastImageSend.year == now.year &&
|
||||||
lastImageSend.month == now.month &&
|
lastImageSend.month == now.month &&
|
||||||
lastImageSend.day == now.day;
|
lastImageSend.day == now.day;
|
||||||
|
|
@ -235,7 +236,7 @@ String formatDateTime(BuildContext context, DateTime? dateTime) {
|
||||||
if (dateTime == null) {
|
if (dateTime == null) {
|
||||||
return 'Never';
|
return 'Never';
|
||||||
}
|
}
|
||||||
final now = DateTime.now();
|
final now = clock.now();
|
||||||
final difference = now.difference(dateTime);
|
final difference = now.difference(dateTime);
|
||||||
|
|
||||||
final date = DateFormat.yMd(Localizations.localeOf(context).toLanguageTag())
|
final date = DateFormat.yMd(Localizations.localeOf(context).toLanguageTag())
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
@ -165,7 +166,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
DateTime? _videoRecordingStarted;
|
DateTime? _videoRecordingStarted;
|
||||||
Timer? _videoRecordingTimer;
|
Timer? _videoRecordingTimer;
|
||||||
|
|
||||||
DateTime _currentTime = DateTime.now();
|
DateTime _currentTime = clock.now();
|
||||||
final GlobalKey keyTriggerButton = GlobalKey();
|
final GlobalKey keyTriggerButton = GlobalKey();
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
|
@ -519,7 +520,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
_videoRecordingTimer =
|
_videoRecordingTimer =
|
||||||
Timer.periodic(const Duration(milliseconds: 15), (timer) {
|
Timer.periodic(const Duration(milliseconds: 15), (timer) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentTime = DateTime.now();
|
_currentTime = clock.now();
|
||||||
});
|
});
|
||||||
if (_videoRecordingStarted != null &&
|
if (_videoRecordingStarted != null &&
|
||||||
_currentTime.difference(_videoRecordingStarted!).inSeconds >=
|
_currentTime.difference(_videoRecordingStarted!).inSeconds >=
|
||||||
|
|
@ -530,7 +531,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setState(() {
|
setState(() {
|
||||||
_videoRecordingStarted = DateTime.now();
|
_videoRecordingStarted = clock.now();
|
||||||
_isVideoRecording = true;
|
_isVideoRecording = true;
|
||||||
});
|
});
|
||||||
} on CameraException catch (e) {
|
} on CameraException catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class VideoRecordingTimer extends StatelessWidget {
|
class VideoRecordingTimer extends StatelessWidget {
|
||||||
|
|
@ -12,7 +13,7 @@ class VideoRecordingTimer extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (videoRecordingStarted != null) {
|
if (videoRecordingStarted != null) {
|
||||||
final currentTime = DateTime.now();
|
final currentTime = clock.now();
|
||||||
return Positioned(
|
return Positioned(
|
||||||
top: 50,
|
top: 50,
|
||||||
left: 0,
|
left: 0,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart';
|
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart';
|
||||||
|
|
@ -9,8 +10,8 @@ class DateTimeFilter extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final currentTime = DateFormat('HH:mm').format(DateTime.now());
|
final currentTime = DateFormat('HH:mm').format(clock.now());
|
||||||
final currentDate = DateFormat('dd.MM.yyyy').format(DateTime.now());
|
final currentDate = DateFormat('dd.MM.yyyy').format(clock.now());
|
||||||
return FilterSkeleton(
|
return FilterSkeleton(
|
||||||
child: Positioned(
|
child: Positioned(
|
||||||
bottom: 80,
|
bottom: 80,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
@ -133,7 +134,7 @@ Future<List<Sticker>> getStickerIndex() async {
|
||||||
|
|
||||||
if (indexFile.existsSync() && kReleaseMode) {
|
if (indexFile.existsSync() && kReleaseMode) {
|
||||||
final lastModified = indexFile.lastModifiedSync();
|
final lastModified = indexFile.lastModifiedSync();
|
||||||
final difference = DateTime.now().difference(lastModified);
|
final difference = clock.now().difference(lastModified);
|
||||||
final content = await indexFile.readAsString();
|
final content = await indexFile.readAsString();
|
||||||
final jsonList = json.decode(content) as List;
|
final jsonList = json.decode(content) as List;
|
||||||
res = jsonList
|
res = jsonList
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -509,7 +510,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
||||||
MediaFilesCompanion(
|
MediaFilesCompanion(
|
||||||
type: Value(mediaService.mediaFile.type),
|
type: Value(mediaService.mediaFile.type),
|
||||||
createdAt: Value(DateTime.now()),
|
createdAt: Value(clock.now()),
|
||||||
stored: const Value(true),
|
stored: const Value(true),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/groups.dao.dart';
|
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
|
import 'package:twonly/src/services/flame.service.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_components/best_friends_selector.dart';
|
import 'package:twonly/src/views/camera/share_image_components/best_friends_selector.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
@ -28,12 +29,13 @@ class _LastMessageTimeState extends State<LastMessageTime> {
|
||||||
if (widget.message != null) {
|
if (widget.message != null) {
|
||||||
final lastAction = await twonlyDB.messagesDao
|
final lastAction = await twonlyDB.messagesDao
|
||||||
.getLastMessageAction(widget.message!.messageId);
|
.getLastMessageAction(widget.message!.messageId);
|
||||||
lastMessageInSeconds = DateTime.now()
|
lastMessageInSeconds = clock
|
||||||
|
.now()
|
||||||
.difference(lastAction?.actionAt ?? widget.message!.createdAt)
|
.difference(lastAction?.actionAt ?? widget.message!.createdAt)
|
||||||
.inSeconds;
|
.inSeconds;
|
||||||
} else if (widget.dateTime != null) {
|
} else if (widget.dateTime != null) {
|
||||||
lastMessageInSeconds =
|
lastMessageInSeconds =
|
||||||
DateTime.now().difference(widget.dateTime!).inSeconds;
|
clock.now().difference(widget.dateTime!).inSeconds;
|
||||||
}
|
}
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart' show DateFormat;
|
import 'package:intl/intl.dart' show DateFormat;
|
||||||
|
|
@ -51,7 +52,7 @@ class FriendlyMessageTime extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
String friendlyTime(BuildContext context, DateTime dt) {
|
String friendlyTime(BuildContext context, DateTime dt) {
|
||||||
final now = DateTime.now();
|
final now = clock.now();
|
||||||
final diff = now.difference(dt);
|
final diff = now.difference(dt);
|
||||||
|
|
||||||
if (diff.inMinutes >= 0 && diff.inMinutes < 60) {
|
if (diff.inMinutes >= 0 && diff.inMinutes < 60) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// ignore_for_file: inference_failure_on_function_invocation
|
// ignore_for_file: inference_failure_on_function_invocation
|
||||||
|
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
@ -113,7 +114,7 @@ class MessageContextMenu extends StatelessWidget {
|
||||||
await twonlyDB.messagesDao.handleMessageDeletion(
|
await twonlyDB.messagesDao.handleMessageDeletion(
|
||||||
null,
|
null,
|
||||||
message.messageId,
|
message.messageId,
|
||||||
DateTime.now(),
|
clock.now(),
|
||||||
);
|
);
|
||||||
await sendCipherTextToGroup(
|
await sendCipherTextToGroup(
|
||||||
message.groupId,
|
message.groupId,
|
||||||
|
|
@ -203,7 +204,7 @@ Future<void> editTextMessage(BuildContext context, Message message) async {
|
||||||
if (newText != null &&
|
if (newText != null &&
|
||||||
newText != message.content &&
|
newText != message.content &&
|
||||||
newText != '') {
|
newText != '') {
|
||||||
final timestamp = DateTime.now();
|
final timestamp = clock.now();
|
||||||
|
|
||||||
await twonlyDB.messagesDao.handleTextEdit(
|
await twonlyDB.messagesDao.handleTextEdit(
|
||||||
null,
|
null,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
@ -291,7 +292,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
}).catchError(Log.error);
|
}).catchError(Log.error);
|
||||||
} else {
|
} else {
|
||||||
if (currentMediaLocal.mediaFile.displayLimitInMilliseconds != null) {
|
if (currentMediaLocal.mediaFile.displayLimitInMilliseconds != null) {
|
||||||
canBeSeenUntil = DateTime.now().add(
|
canBeSeenUntil = clock.now().add(
|
||||||
Duration(
|
Duration(
|
||||||
milliseconds:
|
milliseconds:
|
||||||
currentMediaLocal.mediaFile.displayLimitInMilliseconds!,
|
currentMediaLocal.mediaFile.displayLimitInMilliseconds!,
|
||||||
|
|
@ -314,7 +315,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
nextMediaTimer?.cancel();
|
nextMediaTimer?.cancel();
|
||||||
progressTimer?.cancel();
|
progressTimer?.cancel();
|
||||||
if (canBeSeenUntil != null) {
|
if (canBeSeenUntil != null) {
|
||||||
nextMediaTimer = Timer(canBeSeenUntil!.difference(DateTime.now()), () {
|
nextMediaTimer = Timer(canBeSeenUntil!.difference(clock.now()), () {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
nextMediaOrExit();
|
nextMediaOrExit();
|
||||||
}
|
}
|
||||||
|
|
@ -326,7 +327,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
canBeSeenUntil == null) {
|
canBeSeenUntil == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final difference = canBeSeenUntil!.difference(DateTime.now());
|
final difference = canBeSeenUntil!.difference(clock.now());
|
||||||
// Calculate the progress as a value between 0.0 and 1.0
|
// Calculate the progress as a value between 0.0 and 1.0
|
||||||
progress =
|
progress =
|
||||||
difference.inMilliseconds / (mediaFile.displayLimitInMilliseconds!);
|
difference.inMilliseconds / (mediaFile.displayLimitInMilliseconds!);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
|
@ -23,39 +24,22 @@ class MaxFlameListTitle extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
|
class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
|
||||||
int _flameCounter = 0;
|
Group? _group;
|
||||||
Group? _directChat;
|
|
||||||
late String _groupId;
|
late String _groupId;
|
||||||
|
|
||||||
late StreamSubscription<int> _flameCounterSub;
|
|
||||||
late StreamSubscription<Group?> _groupSub;
|
late StreamSubscription<Group?> _groupSub;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_groupId = getUUIDforDirectChat(widget.contactId, gUser.userId);
|
_groupId = getUUIDforDirectChat(widget.contactId, gUser.userId);
|
||||||
final stream = twonlyDB.groupsDao.watchFlameCounter(_groupId);
|
final stream = twonlyDB.groupsDao.watchGroup(_groupId);
|
||||||
_flameCounterSub = stream.listen((counter) {
|
_groupSub = stream.listen((update) {
|
||||||
if (mounted) {
|
if (mounted) setState(() => _group = update);
|
||||||
setState(() {
|
|
||||||
// in the watchFlameCounter a one is added, so remove this here
|
|
||||||
_flameCounter = counter - 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final stream2 = twonlyDB.groupsDao.watchGroup(_groupId);
|
|
||||||
_groupSub = stream2.listen((update) {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_directChat = update;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_flameCounterSub.cancel();
|
|
||||||
_groupSub.cancel();
|
_groupSub.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -73,13 +57,13 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.info(
|
Log.info(
|
||||||
'Restoring flames from ${_directChat!.flameCounter} to ${_directChat!.maxFlameCounter}',
|
'Restoring flames from ${_group!.flameCounter} to ${_group!.maxFlameCounter}',
|
||||||
);
|
);
|
||||||
await twonlyDB.groupsDao.updateGroup(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
_groupId,
|
_groupId,
|
||||||
GroupsCompanion(
|
GroupsCompanion(
|
||||||
flameCounter: Value(_directChat!.maxFlameCounter),
|
flameCounter: Value(_group!.maxFlameCounter),
|
||||||
lastFlameCounterChange: Value(DateTime.now()),
|
lastFlameCounterChange: Value(clock.now()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await syncFlameCounters(forceForGroup: _groupId);
|
await syncFlameCounters(forceForGroup: _groupId);
|
||||||
|
|
@ -87,11 +71,7 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_directChat == null ||
|
if (_group == null || !isItPossibleToRestoreFlames(_group!)) {
|
||||||
_directChat!.maxFlameCounter <= 2 ||
|
|
||||||
_flameCounter >= _directChat!.maxFlameCounter ||
|
|
||||||
_directChat!.maxFlameCounterFrom!
|
|
||||||
.isBefore(DateTime.now().subtract(const Duration(days: 4)))) {
|
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
return BetterListTile(
|
return BetterListTile(
|
||||||
|
|
@ -102,7 +82,7 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
|
||||||
emoji: '🔥',
|
emoji: '🔥',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
text: 'Restore your ${_directChat!.maxFlameCounter + 1} lost flames',
|
text: 'Restore your ${_group!.maxFlameCounter} lost flames',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
|
@ -65,7 +66,7 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
var lastMonth = '';
|
var lastMonth = '';
|
||||||
galleryItems = [];
|
galleryItems = [];
|
||||||
|
|
||||||
final now = DateTime.now();
|
final now = clock.now();
|
||||||
|
|
||||||
for (final mediaFile in mediaFiles) {
|
for (final mediaFile in mediaFiles) {
|
||||||
final mediaService = MediaFileService(mediaFile);
|
final mediaService = MediaFileService(mediaFile);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:collection' show HashSet;
|
import 'dart:collection' show HashSet;
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -81,7 +82,7 @@ class _DatabaseMigrationViewState extends State<DatabaseMigrationView> {
|
||||||
lastMessageSend: Value(oldContact.lastMessageSend),
|
lastMessageSend: Value(oldContact.lastMessageSend),
|
||||||
flameCounter: Value(oldContact.flameCounter),
|
flameCounter: Value(oldContact.flameCounter),
|
||||||
maxFlameCounter: Value(oldContact.flameCounter),
|
maxFlameCounter: Value(oldContact.flameCounter),
|
||||||
maxFlameCounterFrom: Value(DateTime.now()),
|
maxFlameCounterFrom: Value(clock.now()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.2"
|
version: "0.4.2"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: clock
|
name: clock
|
||||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ dependencies:
|
||||||
web_socket_channel: ^3.0.1
|
web_socket_channel: ^3.0.1
|
||||||
convert: ^3.1.2
|
convert: ^3.1.2
|
||||||
crypto: ^3.0.7
|
crypto: ^3.0.7
|
||||||
|
clock: ^1.1.2
|
||||||
|
|
||||||
|
|
||||||
# Trusted publisher flutter.dev
|
# Trusted publisher flutter.dev
|
||||||
|
|
|
||||||
139
test/features/flame_counter_test.dart
Normal file
139
test/features/flame_counter_test.dart
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift/native.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mutex/mutex.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
|
import 'package:twonly/src/services/flame.service.dart';
|
||||||
|
|
||||||
|
Future<void> expectFlame(DateTime time, String groupId, int counter) async {
|
||||||
|
await withClock(
|
||||||
|
Clock.fixed(time),
|
||||||
|
() async {
|
||||||
|
final group = (await twonlyDB.groupsDao.getGroup(groupId))!;
|
||||||
|
expect(
|
||||||
|
getFlameCounterFromGroup(group),
|
||||||
|
counter,
|
||||||
|
reason: StackTrace.current.toString(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final mutex = Mutex();
|
||||||
|
var usedUserIds = 0;
|
||||||
|
|
||||||
|
Future<int> getAndCreateUserId() async {
|
||||||
|
return mutex.protect<int>(() async {
|
||||||
|
final userId = usedUserIds += 1;
|
||||||
|
await twonlyDB.contactsDao.insertContact(
|
||||||
|
ContactsCompanion(userId: Value(userId), username: Value('$userId')),
|
||||||
|
);
|
||||||
|
return userId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
twonlyDB = TwonlyDB.forTesting(
|
||||||
|
DatabaseConnection(
|
||||||
|
NativeDatabase.memory(),
|
||||||
|
// Recommended for widget tests to avoid test errors.
|
||||||
|
closeStreamsSynchronously: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
gUser = UserData(
|
||||||
|
userId: 0x133337,
|
||||||
|
username: 'test_user',
|
||||||
|
displayName: 'Test User',
|
||||||
|
subscriptionPlan: 'Free',
|
||||||
|
)..appVersion = 62;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test flame counter', () async {
|
||||||
|
final contactId = await getAndCreateUserId();
|
||||||
|
final contact = (await twonlyDB.contactsDao.getContactById(contactId))!;
|
||||||
|
await twonlyDB.groupsDao.createNewDirectChat(
|
||||||
|
contactId,
|
||||||
|
GroupsCompanion(
|
||||||
|
groupName: Value(
|
||||||
|
getContactDisplayName(contact),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final group = (await twonlyDB.groupsDao.getDirectChat(contactId))!;
|
||||||
|
|
||||||
|
await expectFlame(DateTime(2026, 2, 3, 16), group.groupId, 0);
|
||||||
|
|
||||||
|
await withClock(
|
||||||
|
Clock.fixed(DateTime(2026, 2, 2, 16)),
|
||||||
|
() async {
|
||||||
|
await incFlameCounter(group.groupId, true, DateTime(2026, 2, 2, 10));
|
||||||
|
await incFlameCounter(group.groupId, false, DateTime(2026, 2, 2, 15));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await expectFlame(DateTime(2026, 2, 2, 18), group.groupId, 1);
|
||||||
|
await expectFlame(DateTime(2026, 2, 3, 16), group.groupId, 1);
|
||||||
|
await expectFlame(DateTime(2026, 2, 4, 16), group.groupId, 1);
|
||||||
|
await expectFlame(DateTime(2026, 2, 5, 16), group.groupId, 0);
|
||||||
|
|
||||||
|
await withClock(
|
||||||
|
Clock.fixed(DateTime(2026, 2, 5, 16)),
|
||||||
|
() async {
|
||||||
|
await incFlameCounter(group.groupId, true, DateTime(2026, 2, 5, 17));
|
||||||
|
await incFlameCounter(group.groupId, false, DateTime(2026, 2, 5, 18));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await expectFlame(DateTime(2026, 2, 5, 19), group.groupId, 1);
|
||||||
|
|
||||||
|
await withClock(
|
||||||
|
Clock.fixed(DateTime(2026, 2, 6, 1)),
|
||||||
|
() async {
|
||||||
|
await incFlameCounter(group.groupId, true, DateTime(2026, 2, 6, 1));
|
||||||
|
await incFlameCounter(group.groupId, false, DateTime(2026, 2, 6, 2));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await expectFlame(DateTime(2026, 2, 6, 19), group.groupId, 2);
|
||||||
|
await expectFlame(DateTime(2026, 3, 1, 19), group.groupId, 0);
|
||||||
|
|
||||||
|
for (var i = 1; i <= 20; i++) {
|
||||||
|
await withClock(
|
||||||
|
Clock.fixed(DateTime(2026, 3, i, 1)),
|
||||||
|
() async {
|
||||||
|
await incFlameCounter(group.groupId, true, DateTime(2026, 3, i, 2));
|
||||||
|
await incFlameCounter(group.groupId, false, DateTime(2026, 3, i, 3));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await expectFlame(DateTime(2026, 3, 20, 19), group.groupId, 20);
|
||||||
|
await expectFlame(DateTime(2026, 3, 23, 19), group.groupId, 0);
|
||||||
|
|
||||||
|
await withClock(
|
||||||
|
Clock.fixed(DateTime(2026, 3, 24, 19)),
|
||||||
|
() async {
|
||||||
|
final group2 = (await twonlyDB.groupsDao.getGroup(group.groupId))!;
|
||||||
|
expect(isItPossibleToRestoreFlames(group2), true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await withClock(
|
||||||
|
Clock.fixed(DateTime(2026, 3, 25, 19)),
|
||||||
|
() async {
|
||||||
|
final group2 = (await twonlyDB.groupsDao.getGroup(group.groupId))!;
|
||||||
|
expect(isItPossibleToRestoreFlames(group2), false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await twonlyDB.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue