mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 10:58:40 +00:00
fix #200
This commit is contained in:
parent
036c4cab77
commit
588a790554
24 changed files with 171 additions and 97 deletions
|
|
@ -3,6 +3,7 @@ import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
|
import 'package:twonly/src/services/api/media_send.dart';
|
||||||
import 'package:twonly/src/services/notification.service.dart';
|
import 'package:twonly/src/services/notification.service.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/onboarding.view.dart';
|
import 'package:twonly/src/views/onboarding.view.dart';
|
||||||
|
|
@ -71,7 +72,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
|
|
||||||
Future initAsync() async {
|
Future initAsync() async {
|
||||||
setUserPlan();
|
setUserPlan();
|
||||||
apiService.connect();
|
await apiService.connect();
|
||||||
|
// call this function so invalid media files are get purged
|
||||||
|
retryMediaUpload(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ void main() async {
|
||||||
apiService = ApiService();
|
apiService = ApiService();
|
||||||
twonlyDB = TwonlyDatabase();
|
twonlyDB = TwonlyDatabase();
|
||||||
await twonlyDB.messagesDao.resetPendingDownloadState();
|
await twonlyDB.messagesDao.resetPendingDownloadState();
|
||||||
|
await twonlyDB.messagesDao.handleMediaFilesOlderThan7Days();
|
||||||
|
|
||||||
// purge media files in the background
|
// purge media files in the background
|
||||||
purgeReceivedMediaFiles();
|
purgeReceivedMediaFiles();
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,22 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
.write(MessagesCompanion(contentJson: Value(null)));
|
.write(MessagesCompanion(contentJson: Value(null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future handleMediaFilesOlderThan7Days() {
|
||||||
|
/// media files will be deleted by the server after 7 days, so delete them here also
|
||||||
|
return (update(messages)
|
||||||
|
..where(
|
||||||
|
(t) => (t.kind.equals(MessageKind.media.name) &
|
||||||
|
t.openedAt.isNull() &
|
||||||
|
t.messageOtherId.isNull() &
|
||||||
|
(t.sendAt.isSmallerThanValue(
|
||||||
|
DateTime.now().subtract(
|
||||||
|
Duration(days: 8),
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
))
|
||||||
|
.write(MessagesCompanion(errorWhileSending: Value(true)));
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<Message>> getAllMessagesPendingDownloading() {
|
Future<List<Message>> getAllMessagesPendingDownloading() {
|
||||||
return (select(messages)
|
return (select(messages)
|
||||||
..where(
|
..where(
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ enum MessageKind {
|
||||||
flameSync,
|
flameSync,
|
||||||
opened,
|
opened,
|
||||||
ack,
|
ack,
|
||||||
pushKey
|
pushKey,
|
||||||
|
receiveMediaError,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DownloadState {
|
enum DownloadState {
|
||||||
|
|
|
||||||
|
|
@ -228,7 +228,7 @@
|
||||||
"checkoutOptions": "Optionen",
|
"checkoutOptions": "Optionen",
|
||||||
"checkoutPayYearly": "Jährlich bezahlen",
|
"checkoutPayYearly": "Jährlich bezahlen",
|
||||||
"checkoutTotal": "Gesamt",
|
"checkoutTotal": "Gesamt",
|
||||||
"selectPaymentMethode": "Zahlungsmethode auswählen",
|
"selectPaymentMethod": "Zahlungsmethode auswählen",
|
||||||
"twonlyCredit": "twonly-Guthaben",
|
"twonlyCredit": "twonly-Guthaben",
|
||||||
"notEnoughCredit": "Du hast nicht genügend Guthaben!",
|
"notEnoughCredit": "Du hast nicht genügend Guthaben!",
|
||||||
"chargeCredit": "Guthaben aufladen",
|
"chargeCredit": "Guthaben aufladen",
|
||||||
|
|
|
||||||
|
|
@ -386,7 +386,7 @@
|
||||||
"refund": "Refund",
|
"refund": "Refund",
|
||||||
"checkoutPayYearly": "Pay yearly",
|
"checkoutPayYearly": "Pay yearly",
|
||||||
"checkoutTotal": "Total",
|
"checkoutTotal": "Total",
|
||||||
"selectPaymentMethode": "Select Payment Method",
|
"selectPaymentMethod": "Select Payment Method",
|
||||||
"twonlyCredit": "twonly-Credit",
|
"twonlyCredit": "twonly-Credit",
|
||||||
"notEnoughCredit": "You do not have enough credit!",
|
"notEnoughCredit": "You do not have enough credit!",
|
||||||
"chargeCredit": "Charge credit",
|
"chargeCredit": "Charge credit",
|
||||||
|
|
|
||||||
|
|
@ -1382,11 +1382,11 @@ abstract class AppLocalizations {
|
||||||
/// **'Total'**
|
/// **'Total'**
|
||||||
String get checkoutTotal;
|
String get checkoutTotal;
|
||||||
|
|
||||||
/// No description provided for @selectPaymentMethode.
|
/// No description provided for @selectPaymentMethod.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Select Payment Method'**
|
/// **'Select Payment Method'**
|
||||||
String get selectPaymentMethode;
|
String get selectPaymentMethod;
|
||||||
|
|
||||||
/// No description provided for @twonlyCredit.
|
/// No description provided for @twonlyCredit.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -725,7 +725,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
String get checkoutTotal => 'Gesamt';
|
String get checkoutTotal => 'Gesamt';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get selectPaymentMethode => 'Zahlungsmethode auswählen';
|
String get selectPaymentMethod => 'Zahlungsmethode auswählen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get twonlyCredit => 'twonly-Guthaben';
|
String get twonlyCredit => 'twonly-Guthaben';
|
||||||
|
|
|
||||||
|
|
@ -720,7 +720,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
String get checkoutTotal => 'Total';
|
String get checkoutTotal => 'Total';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get selectPaymentMethode => 'Select Payment Method';
|
String get selectPaymentMethod => 'Select Payment Method';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get twonlyCredit => 'twonly-Credit';
|
String get twonlyCredit => 'twonly-Credit';
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,12 @@ class MessageJson {
|
||||||
final int? messageId;
|
final int? messageId;
|
||||||
DateTime timestamp;
|
DateTime timestamp;
|
||||||
|
|
||||||
MessageJson(
|
MessageJson({
|
||||||
{required this.kind,
|
required this.kind,
|
||||||
this.messageId,
|
this.messageId,
|
||||||
required this.content,
|
required this.content,
|
||||||
required this.timestamp});
|
required this.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class ApiService {
|
||||||
|
|
||||||
// reconnection params
|
// reconnection params
|
||||||
Timer? reconnectionTimer;
|
Timer? reconnectionTimer;
|
||||||
// int _reconnectionDelay = 5;
|
int _reconnectionDelay = 5;
|
||||||
|
|
||||||
final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap();
|
final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap();
|
||||||
IOWebSocketChannel? _channel;
|
IOWebSocketChannel? _channel;
|
||||||
|
|
@ -81,7 +81,7 @@ class ApiService {
|
||||||
if (!globalIsAppInBackground) {
|
if (!globalIsAppInBackground) {
|
||||||
retransmitRawBytes();
|
retransmitRawBytes();
|
||||||
tryTransmitMessages();
|
tryTransmitMessages();
|
||||||
retryMediaUpload();
|
retryMediaUpload(false);
|
||||||
tryDownloadAllMediaFiles();
|
tryDownloadAllMediaFiles();
|
||||||
notifyContactsAboutProfileChange();
|
notifyContactsAboutProfileChange();
|
||||||
twonlyDB.markUpdated();
|
twonlyDB.markUpdated();
|
||||||
|
|
@ -92,6 +92,7 @@ class ApiService {
|
||||||
|
|
||||||
Future onConnected() async {
|
Future onConnected() async {
|
||||||
await authenticate();
|
await authenticate();
|
||||||
|
_reconnectionDelay = 5;
|
||||||
globalCallbackConnectionState(true);
|
globalCallbackConnectionState(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,6 +101,12 @@ class ApiService {
|
||||||
isAuthenticated = false;
|
isAuthenticated = false;
|
||||||
globalCallbackConnectionState(false);
|
globalCallbackConnectionState(false);
|
||||||
await twonlyDB.messagesDao.resetPendingDownloadState();
|
await twonlyDB.messagesDao.resetPendingDownloadState();
|
||||||
|
reconnectionTimer ??= Timer(Duration(seconds: _reconnectionDelay), () {
|
||||||
|
Log.info("starting with reconnection.");
|
||||||
|
reconnectionTimer = null;
|
||||||
|
connect();
|
||||||
|
});
|
||||||
|
_reconnectionDelay += 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future close(Function callback) async {
|
Future close(Function callback) async {
|
||||||
|
|
@ -113,11 +120,13 @@ class ApiService {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> connect() async {
|
Future<bool> connect({bool force = false}) async {
|
||||||
|
if (reconnectionTimer != null && !force) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
reconnectionTimer?.cancel();
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user != null && user.isDemoUser) {
|
if (user != null && user.isDemoUser) {
|
||||||
print("DEMO user");
|
|
||||||
// the demo user should not be able to connect to the API server...
|
|
||||||
globalCallbackConnectionState(true);
|
globalCallbackConnectionState(true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -126,9 +135,7 @@ class ApiService {
|
||||||
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.
|
||||||
if (reconnectionTimer != null) {
|
reconnectionTimer?.cancel();
|
||||||
reconnectionTimer!.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
isAuthenticated = false;
|
isAuthenticated = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,7 @@ import 'package:http/http.dart' as http;
|
||||||
// import 'package:twonly/src/providers/api/api_utils.dart';
|
// import 'package:twonly/src/providers/api/api_utils.dart';
|
||||||
import 'package:twonly/src/services/api/media_send.dart';
|
import 'package:twonly/src/services/api/media_send.dart';
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/client_to_server.pb.dart'
|
import 'package:twonly/src/services/api/utils.dart';
|
||||||
as client;
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
|
|
@ -75,7 +74,8 @@ Future<bool> isAllowedToDownload(bool isVideo) async {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future startDownloadMedia(Message message, bool force) async {
|
Future startDownloadMedia(Message message, bool force,
|
||||||
|
{int retryCounter = 0}) async {
|
||||||
if (message.contentJson == null) return;
|
if (message.contentJson == null) return;
|
||||||
if (downloadStartedForMediaReceived[message.messageId] != null) {
|
if (downloadStartedForMediaReceived[message.messageId] != null) {
|
||||||
DateTime started = downloadStartedForMediaReceived[message.messageId]!;
|
DateTime started = downloadStartedForMediaReceived[message.messageId]!;
|
||||||
|
|
@ -151,12 +151,10 @@ Future startDownloadMedia(Message message, bool force) async {
|
||||||
}, onDone: () async {
|
}, onDone: () async {
|
||||||
if (r.statusCode != 200) {
|
if (r.statusCode != 200) {
|
||||||
Log.error("Download error: $r");
|
Log.error("Download error: $r");
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
if (r.statusCode == 418) {
|
||||||
message.messageId,
|
Log.error("Got custom error code: ${chunks.toList()}");
|
||||||
MessagesCompanion(
|
handleMediaError(message);
|
||||||
errorWhileSending: Value(true),
|
}
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,13 +170,18 @@ Future startDownloadMedia(Message message, bool force) async {
|
||||||
offset += chunk.length;
|
offset += chunk.length;
|
||||||
}
|
}
|
||||||
await writeMediaFile(message.messageId, "encrypted", bytes);
|
await writeMediaFile(message.messageId, "encrypted", bytes);
|
||||||
handleEncryptedFile(message, encryptedBytesTmp: bytes);
|
handleEncryptedFile(message,
|
||||||
|
encryptedBytesTmp: bytes, retryCounter: retryCounter);
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future handleEncryptedFile(Message msg, {Uint8List? encryptedBytesTmp}) async {
|
Future handleEncryptedFile(
|
||||||
|
Message msg, {
|
||||||
|
Uint8List? encryptedBytesTmp,
|
||||||
|
int retryCounter = 0,
|
||||||
|
}) async {
|
||||||
Uint8List? encryptedBytes =
|
Uint8List? encryptedBytes =
|
||||||
encryptedBytesTmp ?? await readMediaFile(msg.messageId, "encrypted");
|
encryptedBytesTmp ?? await readMediaFile(msg.messageId, "encrypted");
|
||||||
|
|
||||||
|
|
@ -211,16 +214,16 @@ Future handleEncryptedFile(Message msg, {Uint8List? encryptedBytesTmp}) async {
|
||||||
|
|
||||||
await writeMediaFile(msg.messageId, "png", imageBytes);
|
await writeMediaFile(msg.messageId, "png", imageBytes);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error("Decryption error: $e");
|
if (retryCounter >= 1) {
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
Log.error(
|
||||||
msg.messageId,
|
"could not decrypt the media file in the second try. reporting error to user: $e");
|
||||||
MessagesCompanion(
|
handleMediaError(msg);
|
||||||
errorWhileSending: Value(true),
|
return;
|
||||||
),
|
}
|
||||||
);
|
Log.error("could not decrypt the media file trying again: $e");
|
||||||
// answers with ok, so the server will delete the message
|
startDownloadMedia(msg, true, retryCounter: retryCounter + 1);
|
||||||
var ok = client.Response_Ok()..none = true;
|
// try downloading again....
|
||||||
return client.Response()..ok = ok;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ Future<bool> checkForFailedUploads() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
final lockingHandleMediaFile = Mutex();
|
final lockingHandleMediaFile = Mutex();
|
||||||
Future retryMediaUpload({int maxRetries = 3}) async {
|
Future retryMediaUpload(bool appRestarted, {int maxRetries = 3}) async {
|
||||||
if (maxRetries == 0) {
|
if (maxRetries == 0) {
|
||||||
Log.error("retried media upload 3 times. abort retrying");
|
Log.error("retried media upload 3 times. abort retrying");
|
||||||
return;
|
return;
|
||||||
|
|
@ -116,16 +116,23 @@ Future retryMediaUpload({int maxRetries = 3}) async {
|
||||||
Log.info("re uploading ${mediaFiles.length} media files.");
|
Log.info("re uploading ${mediaFiles.length} media files.");
|
||||||
for (final mediaFile in mediaFiles) {
|
for (final mediaFile in mediaFiles) {
|
||||||
if (mediaFile.messageIds == null || mediaFile.metadata == null) {
|
if (mediaFile.messageIds == null || mediaFile.metadata == null) {
|
||||||
// the media upload was canceled,
|
if (appRestarted) {
|
||||||
if (mediaFile.uploadTokens != null) {
|
/// When the app got restarted and the messageIds or the metadata is not
|
||||||
/// the file was already uploaded.
|
/// set then the app was closed before the images was send.
|
||||||
/// notify the server to remove the upload
|
|
||||||
apiService.getDownloadTokens(mediaFile.uploadTokens!.uploadToken, 0);
|
// the media upload was canceled,
|
||||||
|
if (mediaFile.uploadTokens != null) {
|
||||||
|
/// the file was already uploaded.
|
||||||
|
/// notify the server to remove the upload
|
||||||
|
apiService.getDownloadTokens(
|
||||||
|
mediaFile.uploadTokens!.uploadToken, 0);
|
||||||
|
}
|
||||||
|
await twonlyDB.mediaUploadsDao
|
||||||
|
.deleteMediaUpload(mediaFile.mediaUploadId);
|
||||||
|
Log.info(
|
||||||
|
"upload can be removed, the finalized function was never called...",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await twonlyDB.mediaUploadsDao
|
|
||||||
.deleteMediaUpload(mediaFile.mediaUploadId);
|
|
||||||
Log.info(
|
|
||||||
"upload can be removed, the finalized function was never called...");
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,7 +145,7 @@ Future retryMediaUpload({int maxRetries = 3}) async {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
if (retry) {
|
if (retry) {
|
||||||
await retryMediaUpload(maxRetries: maxRetries - 1);
|
await retryMediaUpload(false, maxRetries: maxRetries - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,7 @@ Future<Map<String, dynamic>> getAllMessagesForRetransmitting() async {
|
||||||
|
|
||||||
Future<Result> sendRetransmitMessage(
|
Future<Result> sendRetransmitMessage(
|
||||||
String stateId, RetransmitMessage msg) async {
|
String stateId, RetransmitMessage msg) async {
|
||||||
|
Log.info("Sending ${msg.messageId}");
|
||||||
Result resp =
|
Result resp =
|
||||||
await apiService.sendTextMessage(msg.userId, msg.bytes, msg.pushData);
|
await apiService.sendTextMessage(msg.userId, msg.bytes, msg.pushData);
|
||||||
|
|
||||||
|
|
@ -130,8 +131,8 @@ Future<Result> sendRetransmitMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resp.isSuccess) {
|
if (resp.isSuccess) {
|
||||||
|
retry = false;
|
||||||
if (msg.messageId != null) {
|
if (msg.messageId != null) {
|
||||||
retry = false;
|
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||||
msg.messageId!,
|
msg.messageId!,
|
||||||
MessagesCompanion(acknowledgeByServer: Value(true)),
|
MessagesCompanion(acknowledgeByServer: Value(true)),
|
||||||
|
|
|
||||||
|
|
@ -86,23 +86,35 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case MessageKind.opened:
|
case MessageKind.receiveMediaError:
|
||||||
final update = MessagesCompanion(openedAt: Value(message.timestamp));
|
if (message.messageId != null) {
|
||||||
await twonlyDB.messagesDao.updateMessageByOtherUser(
|
await twonlyDB.messagesDao.updateMessageByOtherUser(
|
||||||
fromUserId,
|
|
||||||
message.messageId!,
|
|
||||||
update,
|
|
||||||
);
|
|
||||||
final openedMessage = await twonlyDB.messagesDao
|
|
||||||
.getMessageByMessageId(message.messageId!)
|
|
||||||
.getSingleOrNull();
|
|
||||||
if (openedMessage != null &&
|
|
||||||
openedMessage.kind == MessageKind.textMessage) {
|
|
||||||
await twonlyDB.messagesDao.openedAllNonMediaMessagesFromOtherUser(
|
|
||||||
fromUserId,
|
fromUserId,
|
||||||
|
message.messageId!,
|
||||||
|
MessagesCompanion(
|
||||||
|
errorWhileSending: Value(true),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case MessageKind.opened:
|
||||||
|
if (message.messageId != null) {
|
||||||
|
final update = MessagesCompanion(openedAt: Value(message.timestamp));
|
||||||
|
await twonlyDB.messagesDao.updateMessageByOtherUser(
|
||||||
|
fromUserId,
|
||||||
|
message.messageId!,
|
||||||
|
update,
|
||||||
|
);
|
||||||
|
final openedMessage = await twonlyDB.messagesDao
|
||||||
|
.getMessageByMessageId(message.messageId!)
|
||||||
|
.getSingleOrNull();
|
||||||
|
if (openedMessage != null &&
|
||||||
|
openedMessage.kind == MessageKind.textMessage) {
|
||||||
|
await twonlyDB.messagesDao.openedAllNonMediaMessagesFromOtherUser(
|
||||||
|
fromUserId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageKind.rejectRequest:
|
case MessageKind.rejectRequest:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
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/tables/messages_table.dart';
|
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/client_to_server.pb.dart'
|
import 'package:twonly/src/model/protobuf/api/client_to_server.pb.dart'
|
||||||
as client;
|
as client;
|
||||||
|
|
@ -66,3 +68,23 @@ Future rejectUser(int contactId) async {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future handleMediaError(Message message) async {
|
||||||
|
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||||
|
message.messageId,
|
||||||
|
MessagesCompanion(
|
||||||
|
errorWhileSending: Value(true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (message.messageOtherId != null) {
|
||||||
|
encryptAndSendMessageAsync(
|
||||||
|
null,
|
||||||
|
message.contactId,
|
||||||
|
MessageJson(
|
||||||
|
kind: MessageKind.receiveMediaError,
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
content: MessageContent(),
|
||||||
|
messageId: message.messageOtherId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ String formatDuration(int seconds) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InputDecoration getInputDecoration(context, hintText) {
|
InputDecoration getInputDecoration(BuildContext context, String hintText) {
|
||||||
final primaryColor =
|
final primaryColor =
|
||||||
Theme.of(context).colorScheme.primary; // Get the primary color
|
Theme.of(context).colorScheme.primary; // Get the primary color
|
||||||
return InputDecoration(
|
return InputDecoration(
|
||||||
|
|
@ -279,7 +279,7 @@ Future insertDemoContacts() async {
|
||||||
if (config['accepted'] ?? false) {
|
if (config['accepted'] ?? false) {
|
||||||
for (var i = 0; i < 20; i++) {
|
for (var i = 0; i < 20; i++) {
|
||||||
int chatId = Random().nextInt(chatMessages.length);
|
int chatId = Random().nextInt(chatMessages.length);
|
||||||
int? messageId = await twonlyDB.messagesDao.insertMessage(
|
await twonlyDB.messagesDao.insertMessage(
|
||||||
MessagesCompanion(
|
MessagesCompanion(
|
||||||
contactId: Value(userId),
|
contactId: Value(userId),
|
||||||
kind: Value(MessageKind.textMessage),
|
kind: Value(MessageKind.textMessage),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
Future<bool> isUserCreated() async {
|
Future<bool> isUserCreated() async {
|
||||||
|
|
@ -28,6 +31,17 @@ Future<UserData?> getUser() async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future updateUsersPlan(BuildContext context, String planId) async {
|
||||||
|
context.read<CustomChangeProvider>().plan = planId;
|
||||||
|
var user = await getUser();
|
||||||
|
if (user != null) {
|
||||||
|
user.subscriptionPlan = planId;
|
||||||
|
await updateUser(user);
|
||||||
|
}
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.read<CustomChangeProvider>().updatePlan(planId);
|
||||||
|
}
|
||||||
|
|
||||||
Future updateUser(UserData userData) async {
|
Future updateUser(UserData userData) async {
|
||||||
final storage = FlutterSecureStorage();
|
final storage = FlutterSecureStorage();
|
||||||
storage.write(key: "userData", value: jsonEncode(userData));
|
storage.write(key: "userData", value: jsonEncode(userData));
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
: RefreshIndicator(
|
: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await apiService.close(() {});
|
await apiService.close(() {});
|
||||||
await apiService.connect();
|
await apiService.connect(force: true);
|
||||||
await Future.delayed(Duration(seconds: 1));
|
await Future.delayed(Duration(seconds: 1));
|
||||||
},
|
},
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import 'package:lottie/lottie.dart';
|
||||||
import 'package:no_screenshot/no_screenshot.dart';
|
import 'package:no_screenshot/no_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';
|
||||||
|
import 'package:twonly/src/services/api/utils.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
||||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
|
|
@ -252,13 +253,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
|
|
||||||
if ((imageBytes == null && !content.isVideo) ||
|
if ((imageBytes == null && !content.isVideo) ||
|
||||||
(content.isVideo && videoController == null)) {
|
(content.isVideo && videoController == null)) {
|
||||||
|
Log.error("media files are not found...");
|
||||||
// When the message should be downloaded but imageBytes are null then a error happened
|
// When the message should be downloaded but imageBytes are null then a error happened
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
await handleMediaError(current);
|
||||||
current.messageId,
|
|
||||||
MessagesCompanion(
|
|
||||||
errorWhileSending: Value(true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return nextMediaOrExit();
|
return nextMediaOrExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ class HomeViewState extends State<HomeView> {
|
||||||
offsetRatio = offsetFromOne.abs();
|
offsetRatio = offsetFromOne.abs();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (cameraController == null && !initCameraStarted) {
|
if (cameraController == null && !initCameraStarted && offsetRatio < 1) {
|
||||||
initCameraStarted = true;
|
initCameraStarted = true;
|
||||||
selectCamera(selectedCameraDetails.cameraId, false, false);
|
selectCamera(selectedCameraDetails.cameraId, false, false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ class _CheckoutViewState extends State<CheckoutView> {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(context.lang.selectPaymentMethode)),
|
child: Text(context.lang.selectPaymentMethod)),
|
||||||
),
|
),
|
||||||
SizedBox(height: 20)
|
SizedBox(height: 20)
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
||||||
|
|
@ -82,7 +80,7 @@ class _SelectPaymentViewState extends State<SelectPaymentView> {
|
||||||
(balanceInCents == null || balanceInCents! >= checkoutInCents));
|
(balanceInCents == null || balanceInCents! >= checkoutInCents));
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(context.lang.selectPaymentMethode),
|
title: Text(context.lang.selectPaymentMethod),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -222,21 +220,12 @@ class _SelectPaymentViewState extends State<SelectPaymentView> {
|
||||||
widget.planId!, widget.payMonthly!, tryAutoRenewal);
|
widget.planId!, widget.payMonthly!, tryAutoRenewal);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
context.read<CustomChangeProvider>().plan =
|
await updateUsersPlan(context, widget.planId!);
|
||||||
widget.planId!;
|
|
||||||
var user = await getUser();
|
|
||||||
if (user != null) {
|
|
||||||
user.subscriptionPlan = widget.planId!;
|
|
||||||
await updateUser(user);
|
|
||||||
}
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context
|
|
||||||
.read<CustomChangeProvider>()
|
|
||||||
.updatePlan(widget.planId!);
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content:
|
content: Text(context.lang.planSuccessUpgraded),
|
||||||
Text(context.lang.planSuccessUpgraded)),
|
),
|
||||||
);
|
);
|
||||||
Navigator.of(context).pop(true);
|
Navigator.of(context).pop(true);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -556,12 +556,12 @@ Future redeemUserInviteCode(BuildContext context, String newPlan) async {
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(context.lang.redeemUserInviteCodeSuccess)),
|
content: Text(context.lang.redeemUserInviteCodeSuccess),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
// reconnect to load new plan.
|
// reconnect to load new plan.
|
||||||
apiService.close(() {
|
await apiService.close(() {});
|
||||||
apiService.connect();
|
await apiService.connect();
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(errorCodeToText(context, res.error))),
|
SnackBar(content: Text(errorCodeToText(context, res.error))),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue