mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:28:41 +00:00
bug fixes
This commit is contained in:
parent
1b1da8c481
commit
606d377617
15 changed files with 84 additions and 94 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -5,9 +5,11 @@
|
|||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
|
|
|
|||
|
|
@ -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
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
|
|
@ -178,15 +127,18 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||
if (wasPaused) {
|
||||
globalIsAppInBackground = false;
|
||||
apiProvider.connect();
|
||||
context.read<ContactChangeProvider>().update();
|
||||
context.read<MessagesChangeProvider>().init();
|
||||
// _stopService();
|
||||
}
|
||||
} else if (state == AppLifecycleState.paused) {
|
||||
wasPaused = true;
|
||||
globalIsAppInBackground = true;
|
||||
apiProvider.close(() {
|
||||
|
||||
// apiProvider.close(() {
|
||||
// use this only when uploading an image
|
||||
// _startService();
|
||||
});
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"searchUsernameInput": "Benutzername",
|
||||
"searchUsernameTitle": "Benutzernamen suchen",
|
||||
"searchUsernameNotFound": "Benutzername nicht gefunden",
|
||||
"searchUsernameNotFoundBody": "Es wurde kein Benutzer mit dem Benutzernamen \"{username}\" gefunden.",
|
||||
"searchUsernameNewFollowerTitle": "Folgeanfragen",
|
||||
"searchUsernameQrCodeBtn": "QR-Code scannen",
|
||||
"chatListViewSearchUserNameBtn": "Füge deinen ersten twonly-Kontakt hinzu!",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"searchUsernameInput": "Username",
|
||||
"searchUsernameTitle": "Search username",
|
||||
"searchUsernameNotFound": "Username not found",
|
||||
"searchUsernameNotFoundBody": "There is no user with the username \"{username}\" registered.",
|
||||
"searchUsernameNewFollowerTitle": "Follow requests",
|
||||
"searchUsernameQrCodeBtn": "Scan QR code",
|
||||
"chatListViewSearchUserNameBtn": "Add your first twonly contact!",
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ class DbMessages extends CvModelBase {
|
|||
}
|
||||
|
||||
static Future userOpenedOtherMessage(
|
||||
int otherMessageId, int fromUserId) async {
|
||||
int fromUserId, int otherMessageId) async {
|
||||
Map<String, dynamic> data = {
|
||||
columnMessageOpenedAt: DateTime.now().toIso8601String(),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
|||
Future tryTransmitMessages() async {
|
||||
List<DbMessage> retransmit =
|
||||
await DbMessages.getAllMessagesForRetransmitting();
|
||||
if (retransmit.isEmpty) return;
|
||||
|
||||
debugPrint("tryTransmitMessages: ${retransmit.length}");
|
||||
Logger("api.dart").info("try sending messages: ${retransmit.length}");
|
||||
|
||||
Box box = await getMediaStorage();
|
||||
for (int i = 0; i < retransmit.length; i++) {
|
||||
int msgId = retransmit[i].messageId;
|
||||
debugPrint("msgId=$msgId");
|
||||
|
||||
Uint8List? bytes = box.get("retransmit-$msgId-textmessage");
|
||||
if (bytes != null) {
|
||||
|
|
@ -46,13 +46,8 @@ Future tryTransmitMessages() async {
|
|||
if (encryptedMedia != null) {
|
||||
final content = retransmit[i].messageContent;
|
||||
if (content is MediaMessageContent) {
|
||||
await uploadMediaFile(
|
||||
msgId,
|
||||
Int64(retransmit[i].otherUserId),
|
||||
encryptedMedia,
|
||||
content.isRealTwonly,
|
||||
content.maxShowTime,
|
||||
retransmit[i].sendAt);
|
||||
uploadMediaFile(msgId, 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();
|
||||
if (msg.messageId != null) {
|
||||
debugPrint("putting=${msg.messageId}");
|
||||
box.put("retransmit-${msg.messageId}-textmessage", bytes);
|
||||
}
|
||||
|
||||
|
|
@ -118,6 +112,7 @@ Future uploadMediaFile(
|
|||
DateTime messageSendAt,
|
||||
) async {
|
||||
Box box = await getMediaStorage();
|
||||
Logger("api.dart").info("Uploading image $messageId");
|
||||
|
||||
List<int>? uploadToken = box.get("retransmit-$messageId-uploadtoken");
|
||||
if (uploadToken == null) {
|
||||
|
|
@ -135,10 +130,10 @@ Future uploadMediaFile(
|
|||
|
||||
int offset = box.get("retransmit-$messageId-offset") ?? 0;
|
||||
|
||||
int fragmentedTransportSize = 100000;
|
||||
int fragmentedTransportSize = 50000;
|
||||
|
||||
while (offset < encryptedMedia.length) {
|
||||
debugPrint("offset: $offset");
|
||||
Logger("api.dart").info("Uploading image $messageId with offset: $offset");
|
||||
int end = encryptedMedia.length;
|
||||
if (offset + fragmentedTransportSize < encryptedMedia.length) {
|
||||
end = offset + fragmentedTransportSize;
|
||||
|
|
@ -162,8 +157,6 @@ Future uploadMediaFile(
|
|||
offset = end;
|
||||
}
|
||||
|
||||
Logger("api.dart").shout("DOING UPDATE");
|
||||
|
||||
box.delete("retransmit-$messageId-media");
|
||||
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 {
|
||||
await DbMessages.userOpenedOtherMessage(messageOtherId, fromUserId);
|
||||
await DbMessages.userOpenedOtherMessage(fromUserId, messageOtherId);
|
||||
|
||||
encryptAndSendMessage(
|
||||
Int64(fromUserId),
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
|||
// download should only be done when the app is open
|
||||
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();
|
||||
|
||||
String boxId = data.uploadToken.toString();
|
||||
|
|
@ -83,6 +84,8 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
|||
Uint8List downloadedBytes;
|
||||
if (buffered != null) {
|
||||
if (data.offset != buffered.length) {
|
||||
Logger("server_messages")
|
||||
.info("server send wrong offset: ${data.offset} ${buffered.length}");
|
||||
// Logger("handleDownloadData").error(object)
|
||||
return client.Response()..error = ErrorCode.InvalidOffset;
|
||||
}
|
||||
|
|
@ -104,6 +107,9 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
|||
|
||||
if (rawBytes != null) {
|
||||
box.put("${data.uploadToken}_downloaded", rawBytes);
|
||||
} else {
|
||||
Logger("server_messages")
|
||||
.shout("error decrypting the message: ${data.uploadToken}");
|
||||
}
|
||||
|
||||
box.delete(boxId);
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ import 'package:web_socket_channel/web_socket_channel.dart';
|
|||
/// It handles errors and does automatically tries to reconnect on
|
||||
/// errors or network changes.
|
||||
class ApiProvider {
|
||||
final String apiUrl = kDebugMode
|
||||
final String apiUrl = (kDebugMode && false)
|
||||
? "ws://10.99.0.6:3030/api/client"
|
||||
: "wss://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"
|
||||
: "wss://api2.twonly.eu/api/client";
|
||||
bool isAuthenticated = false;
|
||||
|
|
@ -70,10 +70,13 @@ class ApiProvider {
|
|||
globalCallbackConnectionState(true);
|
||||
_reconnectionDelay = 5;
|
||||
|
||||
if (!globalIsAppInBackground) {
|
||||
tryTransmitMessages();
|
||||
}
|
||||
}
|
||||
|
||||
Future close(Function callback) async {
|
||||
log.info("Closing the websocket connection!");
|
||||
if (_channel != null) {
|
||||
await _channel!.sink.close();
|
||||
callback();
|
||||
|
|
@ -83,7 +86,7 @@ class ApiProvider {
|
|||
}
|
||||
|
||||
Future<bool> connect() async {
|
||||
if (_channel != null && _channel!.closeCode != null) {
|
||||
if (_channel != null) {
|
||||
return true;
|
||||
}
|
||||
// ensure that the connect function is not called again by the timer.
|
||||
|
|
@ -93,13 +96,13 @@ class ApiProvider {
|
|||
|
||||
isAuthenticated = false;
|
||||
|
||||
log.info("Trying to connect to the backend $apiUrl!");
|
||||
log.fine("Trying to connect to the backend $apiUrl!");
|
||||
if (await _connectTo(apiUrl)) {
|
||||
await onConnected();
|
||||
return true;
|
||||
}
|
||||
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!)) {
|
||||
await onConnected();
|
||||
return true;
|
||||
|
|
@ -111,6 +114,7 @@ class ApiProvider {
|
|||
bool get isConnected => _channel != null && _channel!.closeCode != null;
|
||||
|
||||
void _onDone() {
|
||||
log.info("WebSocket Closed");
|
||||
globalCallbackConnectionState(false);
|
||||
_channel = null;
|
||||
isAuthenticated = false;
|
||||
|
|
@ -118,7 +122,7 @@ class ApiProvider {
|
|||
}
|
||||
|
||||
void _onError(dynamic e) {
|
||||
log.shout("WebSocket Error: $e");
|
||||
log.info("WebSocket Error: $e");
|
||||
globalCallbackConnectionState(false);
|
||||
_channel = null;
|
||||
isAuthenticated = false;
|
||||
|
|
@ -126,6 +130,8 @@ class ApiProvider {
|
|||
}
|
||||
|
||||
void tryToReconnect() {
|
||||
return;
|
||||
if (globalIsAppInBackground) return;
|
||||
if (reconnectionTimer != null) {
|
||||
reconnectionTimer!.cancel();
|
||||
}
|
||||
|
|
@ -186,6 +192,7 @@ class ApiProvider {
|
|||
Future<Result> _sendRequestV0(ClientToServer request,
|
||||
{bool authenticated = true}) async {
|
||||
if (_channel == null) {
|
||||
log.shout("sending request, but api is not connected.");
|
||||
if (!await connect()) {
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
|
|
@ -209,8 +216,13 @@ class ApiProvider {
|
|||
isAuthenticated = false;
|
||||
if (authenticated) {
|
||||
await authenticate();
|
||||
if (isAuthenticated) {
|
||||
// this will send the request one more time.
|
||||
return _sendRequestV0(request, authenticated: false);
|
||||
} else {
|
||||
log.shout("Session is not authenticated.");
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|||
|
||||
final stopwatch = Stopwatch()..start();
|
||||
while (!gotMessage) {
|
||||
print("gotMessage: $gotMessage");
|
||||
if (stopwatch.elapsed >= Duration(seconds: 20)) {
|
||||
Logger("firebase-background").shout('Timeout reached. Exiting the loop.');
|
||||
break; // Exit the loop if the timeout is reached.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ Future<UserData?> getUser() async {
|
|||
}
|
||||
try {
|
||||
final userMap = jsonDecode(userJson) as Map<String, dynamic>;
|
||||
Logger("get_user").info("Found user: $userMap");
|
||||
final user = UserData.fromJson(userMap);
|
||||
return user;
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -144,6 +144,13 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
|||
context
|
||||
.read<MessagesChangeProvider>()
|
||||
.loadMessagesForUser(user.userId.toInt());
|
||||
initAsync();
|
||||
}
|
||||
|
||||
Future initAsync() async {
|
||||
context
|
||||
.read<MessagesChangeProvider>()
|
||||
.loadMessagesForUser(user.userId.toInt(), force: true);
|
||||
}
|
||||
|
||||
Future _sendMessage() async {
|
||||
|
|
|
|||
|
|
@ -162,17 +162,31 @@ class _UserListItem extends State<UserListItem> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initAsync();
|
||||
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() {
|
||||
// Change the color every 200 milliseconds
|
||||
updateTime = Timer.periodic(Duration(milliseconds: 200), (timer) {
|
||||
setState(() {
|
||||
lastMessageInSeconds =
|
||||
calculateTimeDifference(DateTime.now(), widget.lastMessage!.sendAt)
|
||||
if (widget.lastMessage != null) {
|
||||
lastMessageInSeconds = calculateTimeDifference(
|
||||
DateTime.now(), widget.lastMessage!.sendAt)
|
||||
.inSeconds;
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,7 +212,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
child: CircularProgressIndicator(
|
||||
value: progress,
|
||||
strokeWidth: 2.0,
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
|||
);
|
||||
}
|
||||
}
|
||||
} else if (context.mounted) {
|
||||
showAlertDialog(context, context.lang.searchUsernameNotFound,
|
||||
context.lang.searchUsernameNotFoundBody(searchUserName.text));
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
publish_to: 'none'
|
||||
|
||||
version: 0.0.1+1
|
||||
version: 0.0.3+3
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.4
|
||||
|
|
|
|||
Loading…
Reference in a new issue