bug fixes

This commit is contained in:
otsmr 2025-02-13 00:20:13 +01:00
parent 1b1da8c481
commit 606d377617
15 changed files with 84 additions and 94 deletions

2
.gitignore vendored
View file

@ -5,9 +5,11 @@
*.swp *.swp
.DS_Store .DS_Store
.atom/ .atom/
.build/
.buildlog/ .buildlog/
.history .history
.svn/ .svn/
.swiftpm/
migrate_working_dir/ migrate_working_dir/
# IntelliJ related # IntelliJ related

View file

@ -120,57 +120,6 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
} }
} }
void _initService() {
FlutterForegroundTask.init(
androidNotificationOptions: AndroidNotificationOptions(
channelId: 'foreground_service',
channelName: 'Foreground Service Notification',
channelDescription:
'This notification appears when the foreground service is running.',
onlyAlertOnce: true,
),
iosNotificationOptions: const IOSNotificationOptions(
showNotification: false,
playSound: false,
),
foregroundTaskOptions: ForegroundTaskOptions(
eventAction: ForegroundTaskEventAction.repeat(5000),
autoRunOnBoot: true,
autoRunOnMyPackageReplaced: true,
allowWakeLock: true,
allowWifiLock: true,
),
);
}
Future<ServiceRequestResult> _startService() async {
if (await FlutterForegroundTask.isRunningService) {
return FlutterForegroundTask.restartService();
} else {
return FlutterForegroundTask.startService(
serviceId: 256,
notificationTitle: 'Staying connected to the server.',
notificationText: 'Tap to return to the app',
notificationIcon:
NotificationIcon(metaDataName: "eu.twonly.service.TWONLY_LOGO"),
notificationInitialRoute: '/chats',
callback: startCallback,
);
}
}
Future _stopService() async {
if (context.mounted) {
context.read<MessagesChangeProvider>().init(afterPaused: true);
context.read<ContactChangeProvider>().update();
}
FlutterForegroundTask.sendDataToTask("");
await FlutterForegroundTask.stopService();
if (!apiProvider.isAuthenticated) {
apiProvider.connect();
}
}
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state); super.didChangeAppLifecycleState(state);
@ -178,15 +127,18 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
if (wasPaused) { if (wasPaused) {
globalIsAppInBackground = false; globalIsAppInBackground = false;
apiProvider.connect(); apiProvider.connect();
context.read<ContactChangeProvider>().update();
context.read<MessagesChangeProvider>().init();
// _stopService(); // _stopService();
} }
} else if (state == AppLifecycleState.paused) { } else if (state == AppLifecycleState.paused) {
wasPaused = true; wasPaused = true;
globalIsAppInBackground = true; globalIsAppInBackground = true;
apiProvider.close(() {
// apiProvider.close(() {
// use this only when uploading an image // use this only when uploading an image
// _startService(); // _startService();
}); // });
} }
} }

View file

@ -34,6 +34,7 @@
"searchUsernameInput": "Benutzername", "searchUsernameInput": "Benutzername",
"searchUsernameTitle": "Benutzernamen suchen", "searchUsernameTitle": "Benutzernamen suchen",
"searchUsernameNotFound": "Benutzername nicht gefunden", "searchUsernameNotFound": "Benutzername nicht gefunden",
"searchUsernameNotFoundBody": "Es wurde kein Benutzer mit dem Benutzernamen \"{username}\" gefunden.",
"searchUsernameNewFollowerTitle": "Folgeanfragen", "searchUsernameNewFollowerTitle": "Folgeanfragen",
"searchUsernameQrCodeBtn": "QR-Code scannen", "searchUsernameQrCodeBtn": "QR-Code scannen",
"chatListViewSearchUserNameBtn": "Füge deinen ersten twonly-Kontakt hinzu!", "chatListViewSearchUserNameBtn": "Füge deinen ersten twonly-Kontakt hinzu!",

View file

@ -34,6 +34,7 @@
"searchUsernameInput": "Username", "searchUsernameInput": "Username",
"searchUsernameTitle": "Search username", "searchUsernameTitle": "Search username",
"searchUsernameNotFound": "Username not found", "searchUsernameNotFound": "Username not found",
"searchUsernameNotFoundBody": "There is no user with the username \"{username}\" registered.",
"searchUsernameNewFollowerTitle": "Follow requests", "searchUsernameNewFollowerTitle": "Follow requests",
"searchUsernameQrCodeBtn": "Scan QR code", "searchUsernameQrCodeBtn": "Scan QR code",
"chatListViewSearchUserNameBtn": "Add your first twonly contact!", "chatListViewSearchUserNameBtn": "Add your first twonly contact!",

View file

@ -325,7 +325,7 @@ class DbMessages extends CvModelBase {
} }
static Future userOpenedOtherMessage( static Future userOpenedOtherMessage(
int otherMessageId, int fromUserId) async { int fromUserId, int otherMessageId) async {
Map<String, dynamic> data = { Map<String, dynamic> data = {
columnMessageOpenedAt: DateTime.now().toIso8601String(), columnMessageOpenedAt: DateTime.now().toIso8601String(),
}; };

View file

@ -19,13 +19,13 @@ import 'package:twonly/src/utils/signal.dart' as SignalHelper;
Future tryTransmitMessages() async { Future tryTransmitMessages() async {
List<DbMessage> retransmit = List<DbMessage> retransmit =
await DbMessages.getAllMessagesForRetransmitting(); await DbMessages.getAllMessagesForRetransmitting();
if (retransmit.isEmpty) return;
debugPrint("tryTransmitMessages: ${retransmit.length}"); Logger("api.dart").info("try sending messages: ${retransmit.length}");
Box box = await getMediaStorage(); Box box = await getMediaStorage();
for (int i = 0; i < retransmit.length; i++) { for (int i = 0; i < retransmit.length; i++) {
int msgId = retransmit[i].messageId; int msgId = retransmit[i].messageId;
debugPrint("msgId=$msgId");
Uint8List? bytes = box.get("retransmit-$msgId-textmessage"); Uint8List? bytes = box.get("retransmit-$msgId-textmessage");
if (bytes != null) { if (bytes != null) {
@ -46,13 +46,8 @@ Future tryTransmitMessages() async {
if (encryptedMedia != null) { if (encryptedMedia != null) {
final content = retransmit[i].messageContent; final content = retransmit[i].messageContent;
if (content is MediaMessageContent) { if (content is MediaMessageContent) {
await uploadMediaFile( uploadMediaFile(msgId, Int64(retransmit[i].otherUserId), encryptedMedia,
msgId, content.isRealTwonly, content.maxShowTime, retransmit[i].sendAt);
Int64(retransmit[i].otherUserId),
encryptedMedia,
content.isRealTwonly,
content.maxShowTime,
retransmit[i].sendAt);
} }
} }
} }
@ -69,7 +64,6 @@ Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
Box box = await getMediaStorage(); Box box = await getMediaStorage();
if (msg.messageId != null) { if (msg.messageId != null) {
debugPrint("putting=${msg.messageId}");
box.put("retransmit-${msg.messageId}-textmessage", bytes); box.put("retransmit-${msg.messageId}-textmessage", bytes);
} }
@ -118,6 +112,7 @@ Future uploadMediaFile(
DateTime messageSendAt, DateTime messageSendAt,
) async { ) async {
Box box = await getMediaStorage(); Box box = await getMediaStorage();
Logger("api.dart").info("Uploading image $messageId");
List<int>? uploadToken = box.get("retransmit-$messageId-uploadtoken"); List<int>? uploadToken = box.get("retransmit-$messageId-uploadtoken");
if (uploadToken == null) { if (uploadToken == null) {
@ -135,10 +130,10 @@ Future uploadMediaFile(
int offset = box.get("retransmit-$messageId-offset") ?? 0; int offset = box.get("retransmit-$messageId-offset") ?? 0;
int fragmentedTransportSize = 100000; int fragmentedTransportSize = 50000;
while (offset < encryptedMedia.length) { while (offset < encryptedMedia.length) {
debugPrint("offset: $offset"); Logger("api.dart").info("Uploading image $messageId with offset: $offset");
int end = encryptedMedia.length; int end = encryptedMedia.length;
if (offset + fragmentedTransportSize < encryptedMedia.length) { if (offset + fragmentedTransportSize < encryptedMedia.length) {
end = offset + fragmentedTransportSize; end = offset + fragmentedTransportSize;
@ -162,8 +157,6 @@ Future uploadMediaFile(
offset = end; offset = end;
} }
Logger("api.dart").shout("DOING UPDATE");
box.delete("retransmit-$messageId-media"); box.delete("retransmit-$messageId-media");
box.delete("retransmit-$messageId-uploadtoken"); box.delete("retransmit-$messageId-uploadtoken");
@ -302,7 +295,7 @@ Future tryDownloadMedia(int messageId, int fromUserId, List<int> mediaToken,
} }
Future userOpenedOtherMessage(int fromUserId, int messageOtherId) async { Future userOpenedOtherMessage(int fromUserId, int messageOtherId) async {
await DbMessages.userOpenedOtherMessage(messageOtherId, fromUserId); await DbMessages.userOpenedOtherMessage(fromUserId, messageOtherId);
encryptAndSendMessage( encryptAndSendMessage(
Int64(fromUserId), Int64(fromUserId),

View file

@ -53,7 +53,8 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
// download should only be done when the app is open // download should only be done when the app is open
return client.Response()..error = ErrorCode.InternalError; return client.Response()..error = ErrorCode.InternalError;
} }
debugPrint("Downloading: ${data.uploadToken} ${data.fin}"); Logger("server_messages")
.info("downloading: ${data.uploadToken} ${data.fin}");
final box = await getMediaStorage(); final box = await getMediaStorage();
String boxId = data.uploadToken.toString(); String boxId = data.uploadToken.toString();
@ -83,6 +84,8 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
Uint8List downloadedBytes; Uint8List downloadedBytes;
if (buffered != null) { if (buffered != null) {
if (data.offset != buffered.length) { if (data.offset != buffered.length) {
Logger("server_messages")
.info("server send wrong offset: ${data.offset} ${buffered.length}");
// Logger("handleDownloadData").error(object) // Logger("handleDownloadData").error(object)
return client.Response()..error = ErrorCode.InvalidOffset; return client.Response()..error = ErrorCode.InvalidOffset;
} }
@ -104,6 +107,9 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
if (rawBytes != null) { if (rawBytes != null) {
box.put("${data.uploadToken}_downloaded", rawBytes); box.put("${data.uploadToken}_downloaded", rawBytes);
} else {
Logger("server_messages")
.shout("error decrypting the message: ${data.uploadToken}");
} }
box.delete(boxId); box.delete(boxId);

View file

@ -25,11 +25,11 @@ import 'package:web_socket_channel/web_socket_channel.dart';
/// It handles errors and does automatically tries to reconnect on /// It handles errors and does automatically tries to reconnect on
/// errors or network changes. /// errors or network changes.
class ApiProvider { class ApiProvider {
final String apiUrl = kDebugMode final String apiUrl = (kDebugMode && false)
? "ws://10.99.0.6:3030/api/client" ? "ws://10.99.0.6:3030/api/client"
: "wss://api.twonly.eu/api/client"; : "wss://api.twonly.eu/api/client";
// ws://api.twonly.eu/api/client // ws://api.twonly.eu/api/client
final String? backupApiUrl = kDebugMode final String? backupApiUrl = (kDebugMode && false)
? "ws://10.99.0.6:3030/api/client" ? "ws://10.99.0.6:3030/api/client"
: "wss://api2.twonly.eu/api/client"; : "wss://api2.twonly.eu/api/client";
bool isAuthenticated = false; bool isAuthenticated = false;
@ -70,10 +70,13 @@ class ApiProvider {
globalCallbackConnectionState(true); globalCallbackConnectionState(true);
_reconnectionDelay = 5; _reconnectionDelay = 5;
if (!globalIsAppInBackground) {
tryTransmitMessages(); tryTransmitMessages();
} }
}
Future close(Function callback) async { Future close(Function callback) async {
log.info("Closing the websocket connection!");
if (_channel != null) { if (_channel != null) {
await _channel!.sink.close(); await _channel!.sink.close();
callback(); callback();
@ -83,7 +86,7 @@ class ApiProvider {
} }
Future<bool> connect() async { Future<bool> connect() async {
if (_channel != null && _channel!.closeCode != null) { if (_channel != null) {
return true; return true;
} }
// ensure that the connect function is not called again by the timer. // ensure that the connect function is not called again by the timer.
@ -93,13 +96,13 @@ class ApiProvider {
isAuthenticated = false; isAuthenticated = false;
log.info("Trying to connect to the backend $apiUrl!"); log.fine("Trying to connect to the backend $apiUrl!");
if (await _connectTo(apiUrl)) { if (await _connectTo(apiUrl)) {
await onConnected(); await onConnected();
return true; return true;
} }
if (backupApiUrl != null) { if (backupApiUrl != null) {
log.info("Trying to connect to the backup backend $backupApiUrl!"); log.fine("Trying to connect to the backup backend $backupApiUrl!");
if (await _connectTo(backupApiUrl!)) { if (await _connectTo(backupApiUrl!)) {
await onConnected(); await onConnected();
return true; return true;
@ -111,6 +114,7 @@ class ApiProvider {
bool get isConnected => _channel != null && _channel!.closeCode != null; bool get isConnected => _channel != null && _channel!.closeCode != null;
void _onDone() { void _onDone() {
log.info("WebSocket Closed");
globalCallbackConnectionState(false); globalCallbackConnectionState(false);
_channel = null; _channel = null;
isAuthenticated = false; isAuthenticated = false;
@ -118,7 +122,7 @@ class ApiProvider {
} }
void _onError(dynamic e) { void _onError(dynamic e) {
log.shout("WebSocket Error: $e"); log.info("WebSocket Error: $e");
globalCallbackConnectionState(false); globalCallbackConnectionState(false);
_channel = null; _channel = null;
isAuthenticated = false; isAuthenticated = false;
@ -126,6 +130,8 @@ class ApiProvider {
} }
void tryToReconnect() { void tryToReconnect() {
return;
if (globalIsAppInBackground) return;
if (reconnectionTimer != null) { if (reconnectionTimer != null) {
reconnectionTimer!.cancel(); reconnectionTimer!.cancel();
} }
@ -186,6 +192,7 @@ class ApiProvider {
Future<Result> _sendRequestV0(ClientToServer request, Future<Result> _sendRequestV0(ClientToServer request,
{bool authenticated = true}) async { {bool authenticated = true}) async {
if (_channel == null) { if (_channel == null) {
log.shout("sending request, but api is not connected.");
if (!await connect()) { if (!await connect()) {
return Result.error(ErrorCode.InternalError); return Result.error(ErrorCode.InternalError);
} }
@ -209,8 +216,13 @@ class ApiProvider {
isAuthenticated = false; isAuthenticated = false;
if (authenticated) { if (authenticated) {
await authenticate(); await authenticate();
if (isAuthenticated) {
// this will send the request one more time. // this will send the request one more time.
return _sendRequestV0(request, authenticated: false); return _sendRequestV0(request, authenticated: false);
} else {
log.shout("Session is not authenticated.");
return Result.error(ErrorCode.InternalError);
}
} }
} }
} }

View file

@ -89,7 +89,6 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
while (!gotMessage) { while (!gotMessage) {
print("gotMessage: $gotMessage");
if (stopwatch.elapsed >= Duration(seconds: 20)) { if (stopwatch.elapsed >= Duration(seconds: 20)) {
Logger("firebase-background").shout('Timeout reached. Exiting the loop.'); Logger("firebase-background").shout('Timeout reached. Exiting the loop.');
break; // Exit the loop if the timeout is reached. break; // Exit the loop if the timeout is reached.

View file

@ -20,7 +20,6 @@ Future<UserData?> getUser() async {
} }
try { try {
final userMap = jsonDecode(userJson) as Map<String, dynamic>; final userMap = jsonDecode(userJson) as Map<String, dynamic>;
Logger("get_user").info("Found user: $userMap");
final user = UserData.fromJson(userMap); final user = UserData.fromJson(userMap);
return user; return user;
} catch (e) { } catch (e) {

View file

@ -144,6 +144,13 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
context context
.read<MessagesChangeProvider>() .read<MessagesChangeProvider>()
.loadMessagesForUser(user.userId.toInt()); .loadMessagesForUser(user.userId.toInt());
initAsync();
}
Future initAsync() async {
context
.read<MessagesChangeProvider>()
.loadMessagesForUser(user.userId.toInt(), force: true);
} }
Future _sendMessage() async { Future _sendMessage() async {

View file

@ -162,17 +162,31 @@ class _UserListItem extends State<UserListItem> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initAsync();
lastUpdateTime(); lastUpdateTime();
} }
Future initAsync() async {
if (widget.lastMessage != null) {
if (!widget.lastMessage!.isDownloaded) {
final content = widget.lastMessage!.messageContent;
if (content is MediaMessageContent) {
tryDownloadMedia(widget.lastMessage!.messageId,
widget.lastMessage!.otherUserId, content.downloadToken);
}
}
}
}
void lastUpdateTime() { void lastUpdateTime() {
// Change the color every 200 milliseconds // Change the color every 200 milliseconds
updateTime = Timer.periodic(Duration(milliseconds: 200), (timer) { updateTime = Timer.periodic(Duration(milliseconds: 200), (timer) {
setState(() { setState(() {
lastMessageInSeconds = if (widget.lastMessage != null) {
calculateTimeDifference(DateTime.now(), widget.lastMessage!.sendAt) lastMessageInSeconds = calculateTimeDifference(
DateTime.now(), widget.lastMessage!.sendAt)
.inSeconds; .inSeconds;
setState(() {}); }
}); });
}); });
} }

View file

@ -212,7 +212,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
child: CircularProgressIndicator( child: CircularProgressIndicator(
value: progress, value: progress,
strokeWidth: 2.0, strokeWidth: 2.0,
)), ),
),
], ],
), ),
), ),

View file

@ -50,6 +50,9 @@ class _SearchUsernameView extends State<SearchUsernameView> {
); );
} }
} }
} else if (context.mounted) {
showAlertDialog(context, context.lang.searchUsernameNotFound,
context.lang.searchUsernameNotFoundBody(searchUserName.text));
} }
setState(() { setState(() {
_isLoading = false; _isLoading = false;

View file

@ -4,7 +4,7 @@ description: "Send pictures to friends in real time and be sure you are the only
# Prevent accidental publishing to pub.dev. # Prevent accidental publishing to pub.dev.
publish_to: 'none' publish_to: 'none'
version: 0.0.1+1 version: 0.0.3+3
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4