Merge pull request #325 from twonlyapp/dev
Some checks failed
Publish on Github / build_and_publish (push) Has been cancelled

Multiple bug fixes
This commit is contained in:
Tobi 2025-11-30 12:54:52 +01:00 committed by GitHub
commit b306165681
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 148 additions and 94 deletions

View file

@ -4,6 +4,14 @@
This repository contains the complete source code of the [twonly](https://twonly.eu) apps. This repository contains the complete source code of the [twonly](https://twonly.eu) apps.
<a href="https://testflight.apple.com/join/U9B3v2rk" >
<img alt="Get it on Testflight button" src="https://twonly.eu/assets/buttons/get-it-on-testflight.png"
width="100px" />
</a>
<a href="https://releases.twonly.eu/fdroid/repo/">
<img alt="Get it on F-Droid button" src="https://twonly.eu/assets/buttons/get-it-on-f-droid.webp" width="100px" />
</a>
## Features ## Features
- Offer a Snapchat™ like experience - Offer a Snapchat™ like experience
@ -54,11 +62,11 @@ run-as eu.twonly.testing ls /data/user/0/eu.twonly.testing/
## Signing Keys ## Signing Keys
When you download the app **via GitHub** you can verify the signing keys using for example the [AppVerifyer](https://github.com/soupslurpr/AppVerifier) and the following SHA-256 fingerprint of the signing certificate. When you download the app **via GitHub or F-Droid** you can verify the signing keys using for example the [AppVerifyer](https://github.com/soupslurpr/AppVerifier) and the following SHA-256 fingerprint of the signing certificate.
eu.twonly eu.twonly
E3:C4:4D:56:8C:67:F9:32:AC:8C:33:90:99:8A:B9:5E:E8:FF:2D:7A:07:3C:24:E3:66:77:93:E6:EA:CD:77:0A E3:C4:4D:56:8C:67:F9:32:AC:8C:33:90:99:8A:B9:5E:E8:FF:2D:7A:07:3C:24:E3:66:77:93:E6:EA:CD:77:0A
## License ## License
This project is licensed under the [GNU AGPL 3.0](LICENSE) license. This project is licensed under the [GNU AGPL 3.0](LICENSE) license.

View file

@ -238,7 +238,7 @@ func getPushNotificationText(pushNotification: PushNotification) -> (String, Str
.text: "sent a message{inGroup}.", .text: "sent a message{inGroup}.",
.twonly: "sent a twonly{inGroup}.", .twonly: "sent a twonly{inGroup}.",
.video: "sent a video{inGroup}.", .video: "sent a video{inGroup}.",
.image: "sent a image{inGroup}.", .image: "sent an image{inGroup}.",
.audio: "sent a voice message{inGroup}.", .audio: "sent a voice message{inGroup}.",
.contactRequest: "wants to connect with you.", .contactRequest: "wants to connect with you.",
.acceptRequest: "is now connected with you.", .acceptRequest: "is now connected with you.",

View file

@ -287,7 +287,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
..where((t) => t.groupId.equals(groupId))) ..where((t) => t.groupId.equals(groupId)))
.getSingle(); .getSingle();
final totalMediaCounter = group.totalMediaCounter + (received ? 0 : 1); final totalMediaCounter = group.totalMediaCounter + 1;
var flameCounter = group.flameCounter; var flameCounter = group.flameCounter;
var maxFlameCounter = group.maxFlameCounter; var maxFlameCounter = group.maxFlameCounter;
var maxFlameCounterFrom = group.maxFlameCounterFrom; var maxFlameCounterFrom = group.maxFlameCounterFrom;

View file

@ -115,7 +115,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
milliseconds: group.deleteMessagesAfterMilliseconds, milliseconds: group.deleteMessagesAfterMilliseconds,
), ),
); );
final affected = await (delete(messages) await (delete(messages)
..where( ..where(
(m) => (m) =>
m.groupId.equals(group.groupId) & m.groupId.equals(group.groupId) &
@ -127,7 +127,6 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
m.createdAt.isSmallerThanValue(deletionTime))), m.createdAt.isSmallerThanValue(deletionTime))),
)) ))
.go(); .go();
Log.info('Deleted $affected messages.');
} }
} }

View file

@ -62,7 +62,7 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
return await (select(receipts)..where((t) => t.rowId.equals(id))) return await (select(receipts)..where((t) => t.rowId.equals(id)))
.getSingle(); .getSingle();
} catch (e) { } catch (e) {
Log.error(e); // ignore error, receipts is already in the database...
return null; return null;
} }
} }

View file

@ -562,7 +562,7 @@
"notificationText": "sent a message{inGroup}.", "notificationText": "sent a message{inGroup}.",
"notificationTwonly": "sent a twonly{inGroup}.", "notificationTwonly": "sent a twonly{inGroup}.",
"notificationVideo": "sent a video{inGroup}.", "notificationVideo": "sent a video{inGroup}.",
"notificationImage": "sent a image{inGroup}.", "notificationImage": "sent an image{inGroup}.",
"notificationAudio": "sent a voice message{inGroup}.", "notificationAudio": "sent a voice message{inGroup}.",
"notificationAddedToGroup": "has added you to \"{groupname}\"", "notificationAddedToGroup": "has added you to \"{groupname}\"",
"notificationContactRequest": "wants to connect with you.", "notificationContactRequest": "wants to connect with you.",

View file

@ -2477,7 +2477,7 @@ abstract class AppLocalizations {
/// No description provided for @notificationImage. /// No description provided for @notificationImage.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'sent a image{inGroup}.'** /// **'sent an image{inGroup}.'**
String notificationImage(Object inGroup); String notificationImage(Object inGroup);
/// No description provided for @notificationAudio. /// No description provided for @notificationAudio.

View file

@ -1345,7 +1345,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String notificationImage(Object inGroup) { String notificationImage(Object inGroup) {
return 'sent a image$inGroup.'; return 'sent an image$inGroup.';
} }
@override @override

View file

@ -185,7 +185,7 @@ class ApiService {
} }
Future<void> _onError(dynamic e) async { Future<void> _onError(dynamic e) async {
Log.error('websocket error: $e'); Log.warn('websocket error: $e');
await onClosed(); await onClosed();
} }
@ -206,7 +206,7 @@ class ApiService {
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async { Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
final startTime = DateTime.now(); final startTime = DateTime.now();
const timeout = Duration(seconds: 20); const timeout = Duration(seconds: 60);
while (true) { while (true) {
if (messagesV0[seq] != null) { if (messagesV0[seq] != null) {
@ -215,7 +215,7 @@ class ApiService {
return tmp; return tmp;
} }
if (DateTime.now().difference(startTime) > timeout) { if (DateTime.now().difference(startTime) > timeout) {
Log.error('Timeout for message $seq'); Log.warn('Timeout for message $seq');
return null; return null;
} }
await Future.delayed(const Duration(milliseconds: 10)); await Future.delayed(const Duration(milliseconds: 10));
@ -283,10 +283,6 @@ class ApiService {
request.v0.seq = seq; request.v0.seq = seq;
final requestBytes = request.writeToBuffer(); final requestBytes = request.writeToBuffer();
Log.info(
'Sending ${requestBytes.length} bytes to the server via WebSocket.',
);
if (ensureRetransmission) { if (ensureRetransmission) {
await addToRetransmissionBuffer(seq, requestBytes); await addToRetransmissionBuffer(seq, requestBytes);
} }
@ -421,7 +417,7 @@ class ApiService {
final result = await sendRequestSync(req, authenticated: false); final result = await sendRequestSync(req, authenticated: false);
if (result.isError) { if (result.isError) {
Log.error('could not request auth challenge', result); Log.warn('could not request auth challenge', result);
return; return;
} }

View file

@ -100,9 +100,11 @@ Future<void> handleDownloadStatusUpdate(TaskStatusUpdate update) async {
failed = false; failed = false;
} else { } else {
failed = true; failed = true;
Log.error( if (update.responseStatusCode != null) {
'Got invalid response status code: ${update.responseStatusCode}', Log.error(
); 'Got invalid response status code: ${update.responseStatusCode}',
);
}
} }
} else { } else {
Log.info('Got ${update.status} for $mediaId'); Log.info('Got ${update.status} for $mediaId');
@ -110,7 +112,6 @@ Future<void> handleDownloadStatusUpdate(TaskStatusUpdate update) async {
} }
if (failed) { if (failed) {
Log.error('Background media upload failed: ${update.status}');
await requestMediaReupload(mediaId); await requestMediaReupload(mediaId);
} else { } else {
await handleEncryptedFile(mediaId); await handleEncryptedFile(mediaId);

View file

@ -44,7 +44,7 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
if (receipt == null) { if (receipt == null) {
receipt = await twonlyDB.receiptsDao.getReceiptById(receiptId!); receipt = await twonlyDB.receiptsDao.getReceiptById(receiptId!);
if (receipt == null) { if (receipt == null) {
Log.error('Receipt not found.'); Log.warn('Receipt not found.');
return null; return null;
} }
} }

View file

@ -134,7 +134,6 @@ Future<void> handleClient2ClientMessage(int fromUserId, Uint8List body) async {
..receiptId = receiptId ..receiptId = receiptId
..type = Message_Type.PLAINTEXT_CONTENT ..type = Message_Type.PLAINTEXT_CONTENT
..plaintextContent = responsePlaintextContent; ..plaintextContent = responsePlaintextContent;
Log.error('Sending decryption error');
} else { } else {
response = Message()..type = Message_Type.SENDER_DELIVERY_RECEIPT; response = Message()..type = Message_Type.SENDER_DELIVERY_RECEIPT;
} }

View file

@ -22,6 +22,13 @@ Future<void> initFCMAfterAuthenticated() async {
final storedToken = await storage.read(key: SecureStorageKeys.googleFcm); final storedToken = await storage.read(key: SecureStorageKeys.googleFcm);
try { try {
if (Platform.isIOS) {
final apnsToken = await FirebaseMessaging.instance.getAPNSToken();
if (apnsToken == null) {
Log.error('Error getting apnsToken');
return;
}
}
final fcmToken = await FirebaseMessaging.instance.getToken(); final fcmToken = await FirebaseMessaging.instance.getToken();
if (fcmToken == null) { if (fcmToken == null) {
Log.error('Error getting fcmToken'); Log.error('Error getting fcmToken');

View file

@ -35,11 +35,7 @@ class MediaFileService {
final service = await MediaFileService.fromMediaId(mediaId); final service = await MediaFileService.fromMediaId(mediaId);
if (service == null) { if (service != null) {
Log.error(
'Purging media file, as it is not in the database $mediaId.',
);
} else {
if (service.mediaFile.isDraftMedia) { if (service.mediaFile.isDraftMedia) {
delete = false; delete = false;
} }

View file

@ -112,7 +112,7 @@ Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)>
return (EncryptedContent.fromBuffer(plaintext), null); return (EncryptedContent.fromBuffer(plaintext), null);
} on InvalidKeyIdException catch (e) { } on InvalidKeyIdException catch (e) {
Log.error(e); Log.warn(e);
return (null, PlaintextContent_DecryptionErrorMessage_Type.PREKEY_UNKNOWN); return (null, PlaintextContent_DecryptionErrorMessage_Type.PREKEY_UNKNOWN);
} catch (e) { } catch (e) {
Log.error(e); Log.error(e);

View file

@ -50,8 +50,7 @@ Future<void> requestNewPrekeysForContact(int contactId) async {
.toList(); .toList();
await twonlyDB.signalDao.insertPreKeys(preKeys); await twonlyDB.signalDao.insertPreKeys(preKeys);
} else { } else {
// 104400 Log.warn('[PREKEY] Could not load new pre keys for user $contactId');
Log.error('[PREKEY] Could not load new pre keys for user $contactId');
} }
}); });
} }
@ -85,7 +84,7 @@ Future<void> requestNewSignedPreKeyForContact(int contactId) async {
), ),
); );
} else { } else {
Log.error('could not load new signed pre key for user $contactId'); Log.warn('could not load new signed pre key for user $contactId');
} }
}); });
} }

View file

@ -203,9 +203,6 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
Future<void> handleBackupStatusUpdate(TaskStatusUpdate update) async { Future<void> handleBackupStatusUpdate(TaskStatusUpdate update) async {
if (update.status == TaskStatus.failed || if (update.status == TaskStatus.failed ||
update.status == TaskStatus.canceled) { update.status == TaskStatus.canceled) {
Log.error(
'twonly Backup upload failed. ${update.responseStatusCode} ${update.responseBody} ${update.responseHeaders} ${update.exception}',
);
await updateUserdata((user) { await updateUserdata((user) {
if (user.twonlySafeBackup != null) { if (user.twonlySafeBackup != null) {
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed; user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed;

View file

@ -18,10 +18,24 @@ void initLogger() {
); );
} }
}); });
cleanLogFile();
} }
class Log { class Log {
static void error(Object? message, [Object? error, StackTrace? stackTrace]) { static String filterLogMessage(String msg) {
if (msg.contains('SqliteException')) {
// Do not log data which would be inserted into the DB.
return msg.substring(0, msg.indexOf('parameters: '));
}
return msg;
}
static void error(
Object? messageInput, [
Object? error,
StackTrace? stackTrace,
]) {
final message = filterLogMessage('$messageInput');
if (globalAllowErrorTrackingViaSentry) { if (globalAllowErrorTrackingViaSentry) {
try { try {
throw Exception(message); throw Exception(message);
@ -32,11 +46,21 @@ class Log {
Logger(_getCallerSourceCodeFilename()).shout(message, error, stackTrace); Logger(_getCallerSourceCodeFilename()).shout(message, error, stackTrace);
} }
static void warn(Object? message, [Object? error, StackTrace? stackTrace]) { static void warn(
Object? messageInput, [
Object? error,
StackTrace? stackTrace,
]) {
final message = filterLogMessage('$messageInput');
Logger(_getCallerSourceCodeFilename()).warning(message, error, stackTrace); Logger(_getCallerSourceCodeFilename()).warning(message, error, stackTrace);
} }
static void info(Object? message, [Object? error, StackTrace? stackTrace]) { static void info(
Object? messageInput, [
Object? error,
StackTrace? stackTrace,
]) {
final message = filterLogMessage('$messageInput');
Logger(_getCallerSourceCodeFilename()).fine(message, error, stackTrace); Logger(_getCallerSourceCodeFilename()).fine(message, error, stackTrace);
} }
} }
@ -77,6 +101,23 @@ Future<void> _writeLogToFile(LogRecord record) async {
}); });
} }
Future<void> cleanLogFile() async {
final directory = await getApplicationSupportDirectory();
final logFile = File('${directory.path}/app.log');
if (logFile.existsSync()) {
final lines = await logFile.readAsLines();
if (lines.length <= 5000) return;
final removeCount = lines.length - 5000;
final remaining = lines.sublist(removeCount);
final sink = logFile.openWrite()..writeAll(remaining, '\n');
await sink.close();
}
}
Future<bool> deleteLogFile() async { Future<bool> deleteLogFile() async {
final directory = await getApplicationSupportDirectory(); final directory = await getApplicationSupportDirectory();
final logFile = File('${directory.path}/app.log'); final logFile = File('${directory.path}/app.log');

View file

@ -6,7 +6,6 @@ import 'dart:math';
import 'package:camera/camera.dart'; import 'package:camera/camera.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/utils/log.dart';
import 'package:twonly/src/views/camera/camera_preview_controller_view.dart'; import 'package:twonly/src/views/camera/camera_preview_controller_view.dart';
class CameraZoomButtons extends StatefulWidget { class CameraZoomButtons extends StatefulWidget {
@ -51,7 +50,6 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
Future<void> initAsync() async { Future<void> initAsync() async {
showWideAngleZoom = (await widget.controller.getMinZoomLevel()) < 1; showWideAngleZoom = (await widget.controller.getMinZoomLevel()) < 1;
Log.info('Found ${gCameras.length} cameras for zoom.');
var index = var index =
gCameras.indexWhere((t) => t.lensType == CameraLensType.ultraWide); gCameras.indexWhere((t) => t.lensType == CameraLensType.ultraWide);

View file

@ -90,11 +90,15 @@ class _TextViewState extends State<TextLayer> {
final bottom = MediaQuery.of(context).viewInsets.bottom + final bottom = MediaQuery.of(context).viewInsets.bottom +
MediaQuery.of(context).viewPadding.bottom; MediaQuery.of(context).viewPadding.bottom;
// On Android it is possible to close the keyboard without `onEditingComplete` is triggered.
if (maxBottomInset > bottom) { if (maxBottomInset > bottom) {
maxBottomInset = 0; // prevent that the text element will be disappearing in case the keyboard just switches for example to the emoji page
if (widget.layerData.isEditing) { if (bottom < 20) {
widget.layerData.isEditing = false; maxBottomInset = 0;
onEditionComplete(); if (widget.layerData.isEditing) {
widget.layerData.isEditing = false;
onEditionComplete();
}
} }
} else { } else {
maxBottomInset = bottom; maxBottomInset = bottom;

View file

@ -6,7 +6,6 @@ import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:hashlib/random.dart';
import 'package:screenshot/screenshot.dart'; import 'package:screenshot/screenshot.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';
@ -431,9 +430,17 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
// In case the image was already stored, then rename the stored image. // In case the image was already stored, then rename the stored image.
if (mediaService.storedPath.existsSync()) { if (mediaService.storedPath.existsSync()) {
final newPath = mediaService.storedPath.absolute.path final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
.replaceFirst(media.mediaId, uuid.v7()); MediaFilesCompanion(
mediaService.storedPath.renameSync(newPath); type: Value(mediaService.mediaFile.type),
createdAt: Value(DateTime.now()),
stored: const Value(true),
),
);
if (mediaFile != null) {
mediaService.storedPath
.renameSync(MediaFileService(mediaFile).storedPath.path);
}
} }
return true; return true;
} }

View file

@ -260,6 +260,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
}); });
final items = await MemoryItem.convertFromMessages(storedMediaFiles); final items = await MemoryItem.convertFromMessages(storedMediaFiles);
if (!mounted) return;
galleryItems = items.values.toList(); galleryItems = items.values.toList();
setState(() {}); setState(() {});
} }

View file

@ -76,11 +76,7 @@ class _MessageInputState extends State<MessageInput> {
} }
void _initializeControllers() { void _initializeControllers() {
recorderController = RecorderController() recorderController = RecorderController();
..androidEncoder = AndroidEncoder.aac
..androidOutputFormat = AndroidOutputFormat.mpeg4
..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC
..sampleRate = 44100;
} }
void _handleTextFocusChange() { void _handleTextFocusChange() {

View file

@ -172,12 +172,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
showSendTextMessageInput = false; showSendTextMessageInput = false;
}); });
// if (Platform.isAndroid) { unawaited(flutterLocalNotificationsPlugin.cancelAll());
// await flutterLocalNotificationsPlugin
// .cancel(allMediaFiles.first.contactId);
// } else {
await flutterLocalNotificationsPlugin.cancelAll();
// }
final stream = final stream =
twonlyDB.mediaFilesDao.watchMedia(allMediaFiles.first.mediaId!); twonlyDB.mediaFilesDao.watchMedia(allMediaFiles.first.mediaId!);
@ -261,6 +256,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
return nextMediaOrExit(); return nextMediaOrExit();
} }
var timerRequired = false;
if (currentMediaLocal.mediaFile.type == MediaType.video) { if (currentMediaLocal.mediaFile.type == MediaType.video) {
videoController = VideoPlayerController.file(currentMediaLocal.tempPath); videoController = VideoPlayerController.file(currentMediaLocal.tempPath);
await videoController?.setLooping( await videoController?.setLooping(
@ -292,12 +289,17 @@ class _MediaViewerViewState extends State<MediaViewerView> {
currentMediaLocal.mediaFile.displayLimitInMilliseconds!, currentMediaLocal.mediaFile.displayLimitInMilliseconds!,
), ),
); );
timerRequired = true;
}
}
if (mounted) {
setState(() {
currentMedia = currentMediaLocal;
});
if (timerRequired) {
startTimer(); startTimer();
} }
} }
setState(() {
currentMedia = currentMediaLocal;
});
} }
void startTimer() { void startTimer() {
@ -310,14 +312,16 @@ class _MediaViewerViewState extends State<MediaViewerView> {
} }
}); });
progressTimer = Timer.periodic(const Duration(milliseconds: 10), (timer) { progressTimer = Timer.periodic(const Duration(milliseconds: 10), (timer) {
if (currentMedia!.mediaFile.displayLimitInMilliseconds == null || final mediaFile = currentMedia?.mediaFile;
if (mediaFile == null) return;
if (mediaFile.displayLimitInMilliseconds == null ||
canBeSeenUntil == null) { canBeSeenUntil == null) {
return; return;
} }
final difference = canBeSeenUntil!.difference(DateTime.now()); final difference = canBeSeenUntil!.difference(DateTime.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 = difference.inMilliseconds / progress =
(currentMedia!.mediaFile.displayLimitInMilliseconds!); difference.inMilliseconds / (mediaFile.displayLimitInMilliseconds!);
setState(() {}); setState(() {});
}); });
} }

View file

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
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/views/components/animate_icon.dart'; import 'package:twonly/src/views/components/animate_icon.dart';
class FlameCounterWidget extends StatefulWidget { class FlameCounterWidget extends StatefulWidget {
@ -38,18 +39,20 @@ class _FlameCounterWidgetState extends State<FlameCounterWidget> {
Future<void> initAsync() async { Future<void> initAsync() async {
var groupId = widget.groupId; var groupId = widget.groupId;
late Group? group;
if (widget.groupId == null && widget.contactId != null) { if (widget.groupId == null && widget.contactId != null) {
final group = await twonlyDB.groupsDao.getDirectChat(widget.contactId!); group = await twonlyDB.groupsDao.getDirectChat(widget.contactId!);
groupId = group?.groupId; groupId = group?.groupId;
} else if (groupId != null) { } else if (groupId != null) {
// do not display the flame counter for groups // do not display the flame counter for groups
final group = await twonlyDB.groupsDao.getGroup(groupId); group = await twonlyDB.groupsDao.getGroup(groupId);
if (!(group?.isDirectChat ?? false)) { if (!(group?.isDirectChat ?? false)) {
return; return;
} }
} }
if (groupId != null) { if (groupId != null && group != null) {
isBestFriend = gUser.myBestFriendGroupId == groupId; isBestFriend =
gUser.myBestFriendGroupId == groupId && group.alsoBestFriend;
final stream = twonlyDB.groupsDao.watchFlameCounter(groupId); final stream = twonlyDB.groupsDao.watchFlameCounter(groupId);
flameCounterSub = stream.listen((counter) { flameCounterSub = stream.listen((counter) {
if (mounted) { if (mounted) {

View file

Before

Width:  |  Height:  |  Size: 7 KiB

After

Width:  |  Height:  |  Size: 7 KiB

View file

Before

Width:  |  Height:  |  Size: 7 KiB

After

Width:  |  Height:  |  Size: 7 KiB

View file

@ -61,10 +61,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: audio_waveforms name: audio_waveforms
sha256: "658fef41bbab299184b65ba2fd749e8ec658c1f7d54a21f7cf97fa96b173b4ce" sha256: "3a34bdd15dd63a6d1501218449048b28ebe8e1f795bf00ec310acd7b70648f07"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "2.0.0"
avatar_maker: avatar_maker:
dependency: "direct main" dependency: "direct main"
description: description:
@ -77,10 +77,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: background_downloader name: background_downloader
sha256: a913b37cc47a656a225e9562b69576000d516f705482f392e2663500e6ff6032 sha256: a3b340e42bc45598918944e378dc6a05877e587fcd0e1b8d2ea26339de87bdf9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.3.0" version: "9.4.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -397,11 +397,12 @@ packages:
emoji_picker_flutter: emoji_picker_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: emoji_picker_flutter path: "."
sha256: "9a44c102079891ea5877f78c70f2e3c6e9df7b7fe0a01757d31f1046eeaa016d" ref: HEAD
url: "https://pub.dev" resolved-ref: c5bffd3414c1e640389b41165b831df7df1cf517
source: hosted url: "https://github.com/otsmr/emoji_picker_flutter.git"
version: "4.3.0" source: git
version: "4.4.0"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -1741,14 +1742,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
universal_io:
dependency: transitive
description:
name: universal_io
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1865,10 +1858,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: video_player name: video_player
sha256: "0d55b1f1a31e5ad4c4967bfaa8ade0240b07d20ee4af1dfef5f531056512961a" sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.10.0" version: "2.10.1"
video_player_android: video_player_android:
dependency: transitive dependency: transitive
description: description:

View file

@ -3,16 +3,16 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
publish_to: 'none' publish_to: 'none'
version: 0.0.71+71 version: 0.0.72+72
environment: environment:
sdk: ^3.6.0 sdk: ^3.6.0
dependencies: dependencies:
archive: ^4.0.7 archive: ^4.0.7
audio_waveforms: ^1.3.0 audio_waveforms: ^2.0.0
avatar_maker: ^0.4.0 avatar_maker: ^0.4.0
background_downloader: ^9.2.2 background_downloader: ^9.4.0
cached_network_image: ^3.4.1 cached_network_image: ^3.4.1
camera: ^0.11.2 camera: ^0.11.2
collection: ^1.18.0 collection: ^1.18.0
@ -74,9 +74,9 @@ dependencies:
sentry_flutter: ^9.8.0 sentry_flutter: ^9.8.0
share_plus: ^12.0.0 share_plus: ^12.0.0
tutorial_coach_mark: ^1.3.0 tutorial_coach_mark: ^1.3.0
url_launcher: ^6.3.1 url_launcher: ^6.3.2
vector_graphics: ^1.1.19 vector_graphics: ^1.1.19
video_player: ^2.9.5 video_player: ^2.10.1
web_socket_channel: ^3.0.1 web_socket_channel: ^3.0.1
dependency_overrides: dependency_overrides:
@ -86,6 +86,11 @@ dependency_overrides:
url: https://github.com/otsmr/flutter-packages.git url: https://github.com/otsmr/flutter-packages.git
path: packages/camera/camera_android_camerax path: packages/camera/camera_android_camerax
ref: aef58af205a5f3ce6588a5c59bb2e734aab943f0 ref: aef58af205a5f3ce6588a5c59bb2e734aab943f0
emoji_picker_flutter:
# Fixes the issue with recent emojis (solved by https://github.com/Fintasys/emoji_picker_flutter/pull/238)
# Using override until this gets merged.
git:
url: https://github.com/otsmr/emoji_picker_flutter.git
flutter_android_volume_keydown: flutter_android_volume_keydown:
git: git:
url: https://github.com/yenchieh/flutter_android_volume_keydown.git url: https://github.com/yenchieh/flutter_android_volume_keydown.git