mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 14:28:40 +00:00
fix #185
This commit is contained in:
parent
87bc1ed824
commit
8cb5e77686
5 changed files with 136 additions and 120 deletions
|
|
@ -8,16 +8,13 @@ import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/providers/api/api_utils.dart';
|
import 'package:http/http.dart' as http;
|
||||||
|
// import 'package:twonly/src/providers/api/api_utils.dart';
|
||||||
import 'package:twonly/src/providers/api/media_send.dart';
|
import 'package:twonly/src/providers/api/media_send.dart';
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:twonly/app.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;
|
||||||
import 'package:twonly/src/model/protobuf/api/error.pb.dart';
|
|
||||||
import 'package:twonly/src/model/protobuf/api/server_to_client.pbserver.dart';
|
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
Map<int, DateTime> downloadStartedForMediaReceived = {};
|
Map<int, DateTime> downloadStartedForMediaReceived = {};
|
||||||
|
|
@ -91,7 +88,6 @@ Future startDownloadMedia(Message message, bool force) async {
|
||||||
|
|
||||||
final content =
|
final content =
|
||||||
MessageContent.fromJson(message.kind, jsonDecode(message.contentJson!));
|
MessageContent.fromJson(message.kind, jsonDecode(message.contentJson!));
|
||||||
|
|
||||||
if (content is! MediaMessageContent) return;
|
if (content is! MediaMessageContent) return;
|
||||||
if (content.downloadToken == null) return;
|
if (content.downloadToken == null) return;
|
||||||
|
|
||||||
|
|
@ -123,100 +119,70 @@ Future startDownloadMedia(Message message, bool force) async {
|
||||||
downloadState: Value(DownloadState.downloading),
|
downloadState: Value(DownloadState.downloading),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
int offset = 0;
|
|
||||||
Uint8List? bytes = await readMediaFile(media.messageId, "encrypted");
|
|
||||||
if (bytes != null && bytes.isNotEmpty) {
|
|
||||||
offset = bytes.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// int offset = 0;
|
||||||
|
// Uint8List? bytes = await readMediaFile(media.messageId, "encrypted");
|
||||||
|
// if (bytes != null && bytes.isNotEmpty) {
|
||||||
|
// offset = bytes.length;
|
||||||
|
|
||||||
downloadStartedForMediaReceived[message.messageId] = DateTime.now();
|
downloadStartedForMediaReceived[message.messageId] = DateTime.now();
|
||||||
Result res =
|
|
||||||
await apiProvider.triggerDownload(content.downloadToken!, offset);
|
String downloadToken = uint8ListToHex(content.downloadToken!);
|
||||||
if (res.isError) {
|
|
||||||
if (res.error == ErrorCode.InvalidDownloadToken) {
|
String apiUrl =
|
||||||
// TODO: notfy the sender about this issue
|
"http${apiProvider.apiSecure}://${apiProvider.apiHost}/api/download/$downloadToken";
|
||||||
|
|
||||||
|
var httpClient = http.Client();
|
||||||
|
var request = http.Request('GET', Uri.parse(apiUrl));
|
||||||
|
var response = httpClient.send(request);
|
||||||
|
|
||||||
|
List<List<int>> chunks = [];
|
||||||
|
int downloaded = 0;
|
||||||
|
|
||||||
|
response.asStream().listen((http.StreamedResponse r) {
|
||||||
|
r.stream.listen((List<int> chunk) {
|
||||||
|
// Display percentage of completion
|
||||||
|
print('downloadPercentage: ${downloaded / (r.contentLength ?? 0) * 100}');
|
||||||
|
|
||||||
|
chunks.add(chunk);
|
||||||
|
downloaded += chunk.length;
|
||||||
|
}, onDone: () async {
|
||||||
|
if (r.statusCode != 200) {
|
||||||
|
Logger("media_received.dart").shout("Download error: $r");
|
||||||
await twonlyDatabase.messagesDao.updateMessageByMessageId(
|
await twonlyDatabase.messagesDao.updateMessageByMessageId(
|
||||||
media.messageId,
|
message.messageId,
|
||||||
MessagesCompanion(
|
MessagesCompanion(
|
||||||
errorWhileSending: Value(true),
|
errorWhileSending: Value(true),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
return;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<client.Response> handleDownloadData(DownloadData data) async {
|
// Display percentage of completion
|
||||||
if (globalIsAppInBackground) {
|
print('downloadPercentage: ${downloaded / (r.contentLength ?? 0) * 100}');
|
||||||
// download should only be done when the app is open
|
|
||||||
return client.Response()..error = ErrorCode.InternalError;
|
// Save the file
|
||||||
|
final Uint8List bytes = Uint8List(r.contentLength ?? 0);
|
||||||
|
int offset = 0;
|
||||||
|
for (List<int> chunk in chunks) {
|
||||||
|
bytes.setRange(offset, offset + chunk.length, chunk);
|
||||||
|
offset += chunk.length;
|
||||||
|
}
|
||||||
|
await writeMediaFile(message.messageId, "encrypted", bytes);
|
||||||
|
handleEncryptedFile(message, encryptedBytesTmp: bytes);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger("server_messages")
|
Future handleEncryptedFile(Message msg, {Uint8List? encryptedBytesTmp}) async {
|
||||||
.info("downloading: ${data.downloadToken} ${data.fin}");
|
Uint8List? encryptedBytes =
|
||||||
|
encryptedBytesTmp ?? await readMediaFile(msg.messageId, "encrypted");
|
||||||
|
|
||||||
final media = await twonlyDatabase.mediaDownloadsDao
|
if (encryptedBytes == null) {
|
||||||
.getMediaDownloadByDownloadToken(data.downloadToken)
|
|
||||||
.getSingleOrNull();
|
|
||||||
|
|
||||||
if (media == null) {
|
|
||||||
Logger("server_messages")
|
|
||||||
.shout("download data received, but unknown messageID");
|
|
||||||
// answers with ok, so the server will delete the message
|
|
||||||
var ok = client.Response_Ok()..none = true;
|
|
||||||
return client.Response()..ok = ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.fin && data.offset == 3_980_938_213 && data.data.isEmpty) {
|
|
||||||
Logger("media_received.dart").shout("Image already deleted by the server!");
|
|
||||||
// media file was deleted by the server. remove the media from device
|
|
||||||
await twonlyDatabase.messagesDao.updateMessageByMessageId(
|
|
||||||
media.messageId,
|
|
||||||
MessagesCompanion(
|
|
||||||
errorWhileSending: Value(true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await deleteMediaFile(media.messageId, "encrypted");
|
|
||||||
var ok = client.Response_Ok()..none = true;
|
|
||||||
return client.Response()..ok = ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List? buffered = await readMediaFile(media.messageId, "encrypted");
|
|
||||||
Uint8List downloadedBytes;
|
|
||||||
if (buffered != null) {
|
|
||||||
if (data.offset != buffered.length) {
|
|
||||||
Logger("media_received.dart")
|
Logger("media_received.dart")
|
||||||
.shout("server send wrong offset: ${data.offset} ${buffered.length}");
|
.shout("encrypted bytes are not found for ${msg.messageId}");
|
||||||
return client.Response()..error = ErrorCode.InvalidOffset;
|
|
||||||
}
|
|
||||||
var b = BytesBuilder();
|
|
||||||
b.add(buffered);
|
|
||||||
b.add(data.data);
|
|
||||||
downloadedBytes = b.takeBytes();
|
|
||||||
} else {
|
|
||||||
downloadedBytes = Uint8List.fromList(data.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeMediaFile(media.messageId, "encrypted", downloadedBytes);
|
|
||||||
|
|
||||||
if (!data.fin) {
|
|
||||||
// download not finished, so waiting for more data...
|
|
||||||
var ok = client.Response_Ok()..none = true;
|
|
||||||
return client.Response()..ok = ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
Message? msg = await twonlyDatabase.messagesDao
|
|
||||||
.getMessageByMessageId(media.messageId)
|
|
||||||
.getSingleOrNull();
|
|
||||||
|
|
||||||
if (msg == null) {
|
|
||||||
await deleteMediaFile(media.messageId, "encrypted");
|
|
||||||
Logger("media_received.dart")
|
|
||||||
.info("messageId not found in database. Ignoring download");
|
|
||||||
// answers with ok, so the server will delete the message
|
|
||||||
var ok = client.Response_Ok()..none = true;
|
|
||||||
return client.Response()..ok = ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaMessageContent content =
|
MediaMessageContent content =
|
||||||
|
|
@ -226,7 +192,7 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
||||||
SecretKeyData secretKeyData = SecretKeyData(content.encryptionKey!);
|
SecretKeyData secretKeyData = SecretKeyData(content.encryptionKey!);
|
||||||
|
|
||||||
SecretBox secretBox = SecretBox(
|
SecretBox secretBox = SecretBox(
|
||||||
downloadedBytes,
|
encryptedBytes!,
|
||||||
nonce: content.encryptionNonce!,
|
nonce: content.encryptionNonce!,
|
||||||
mac: Mac(content.encryptionMac!),
|
mac: Mac(content.encryptionMac!),
|
||||||
);
|
);
|
||||||
|
|
@ -239,14 +205,14 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
||||||
if (content.isVideo) {
|
if (content.isVideo) {
|
||||||
final splited = extractUint8Lists(imageBytes);
|
final splited = extractUint8Lists(imageBytes);
|
||||||
imageBytes = splited[0];
|
imageBytes = splited[0];
|
||||||
await writeMediaFile(media.messageId, "mp4", splited[1]);
|
await writeMediaFile(msg.messageId, "mp4", splited[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeMediaFile(media.messageId, "png", imageBytes);
|
await writeMediaFile(msg.messageId, "png", imageBytes);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger("media_received.dart").info("Decryption error: $e");
|
Logger("media_received.dart").info("Decryption error: $e");
|
||||||
await twonlyDatabase.messagesDao.updateMessageByMessageId(
|
await twonlyDatabase.messagesDao.updateMessageByMessageId(
|
||||||
media.messageId,
|
msg.messageId,
|
||||||
MessagesCompanion(
|
MessagesCompanion(
|
||||||
errorWhileSending: Value(true),
|
errorWhileSending: Value(true),
|
||||||
),
|
),
|
||||||
|
|
@ -257,14 +223,13 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
await twonlyDatabase.messagesDao.updateMessageByMessageId(
|
await twonlyDatabase.messagesDao.updateMessageByMessageId(
|
||||||
media.messageId,
|
msg.messageId,
|
||||||
MessagesCompanion(downloadState: Value(DownloadState.downloaded)),
|
MessagesCompanion(downloadState: Value(DownloadState.downloaded)),
|
||||||
);
|
);
|
||||||
|
|
||||||
await deleteMediaFile(media.messageId, "encrypted");
|
await deleteMediaFile(msg.messageId, "encrypted");
|
||||||
|
|
||||||
var ok = client.Response_Ok()..none = true;
|
apiProvider.downloadDone(content.downloadToken!);
|
||||||
return client.Response()..ok = ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List?> getImageBytes(int mediaId) async {
|
Future<Uint8List?> getImageBytes(int mediaId) async {
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ Future<int?> initMediaUpload() async {
|
||||||
|
|
||||||
Future<bool> addVideoToUpload(int mediaUploadId, File videoFilePath) async {
|
Future<bool> addVideoToUpload(int mediaUploadId, File videoFilePath) async {
|
||||||
String basePath = await getMediaFilePath(mediaUploadId, "send");
|
String basePath = await getMediaFilePath(mediaUploadId, "send");
|
||||||
await videoFilePath.rename("$basePath.original.mp4");
|
await videoFilePath.copy("$basePath.original.mp4");
|
||||||
return await compressVideoIfExists(mediaUploadId);
|
return await compressVideoIfExists(mediaUploadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -499,7 +499,7 @@ Future<bool> handleNotifyReceiver(MediaUpload media) async {
|
||||||
|
|
||||||
Future<bool> compressVideoIfExists(int mediaUploadId) async {
|
Future<bool> compressVideoIfExists(int mediaUploadId) async {
|
||||||
String basePath = await getMediaFilePath(mediaUploadId, "send");
|
String basePath = await getMediaFilePath(mediaUploadId, "send");
|
||||||
File videoOriginalFile = File("$basePath.orginal.mp4");
|
File videoOriginalFile = File("$basePath.original.mp4");
|
||||||
File videoCompressedFile = File("$basePath.mp4");
|
File videoCompressedFile = File("$basePath.mp4");
|
||||||
|
|
||||||
if (videoCompressedFile.existsSync()) {
|
if (videoCompressedFile.existsSync()) {
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,6 @@ Future handleServerMessage(server.ServerToClient msg) async {
|
||||||
Uint8List body = Uint8List.fromList(msg.v0.newMessage.body);
|
Uint8List body = Uint8List.fromList(msg.v0.newMessage.body);
|
||||||
int fromUserId = msg.v0.newMessage.fromUserId.toInt();
|
int fromUserId = msg.v0.newMessage.fromUserId.toInt();
|
||||||
response = await handleNewMessage(fromUserId, body);
|
response = await handleNewMessage(fromUserId, body);
|
||||||
} else if (msg.v0.hasDownloaddata()) {
|
|
||||||
response = await handleDownloadData(msg.v0.downloaddata);
|
|
||||||
} else {
|
} else {
|
||||||
Logger("handleServerMessage")
|
Logger("handleServerMessage")
|
||||||
.shout("Got a new message from the server: $msg");
|
.shout("Got a new message from the server: $msg");
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import 'package:twonly/src/providers/api/api_utils.dart';
|
||||||
import 'package:twonly/src/providers/api/media_received.dart';
|
import 'package:twonly/src/providers/api/media_received.dart';
|
||||||
import 'package:twonly/src/providers/api/media_send.dart';
|
import 'package:twonly/src/providers/api/media_send.dart';
|
||||||
import 'package:twonly/src/providers/api/server_messages.dart';
|
import 'package:twonly/src/providers/api/server_messages.dart';
|
||||||
|
import 'package:twonly/src/providers/hive.dart';
|
||||||
import 'package:twonly/src/services/fcm_service.dart';
|
import 'package:twonly/src/services/fcm_service.dart';
|
||||||
import 'package:twonly/src/services/flame_service.dart';
|
import 'package:twonly/src/services/flame_service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
@ -77,6 +78,7 @@ class ApiProvider {
|
||||||
globalCallbackConnectionState(true);
|
globalCallbackConnectionState(true);
|
||||||
|
|
||||||
if (!globalIsAppInBackground) {
|
if (!globalIsAppInBackground) {
|
||||||
|
retransmitRawBytes();
|
||||||
tryTransmitMessages();
|
tryTransmitMessages();
|
||||||
retryMediaUpload();
|
retryMediaUpload();
|
||||||
tryDownloadAllMediaFiles();
|
tryDownloadAllMediaFiles();
|
||||||
|
|
@ -148,6 +150,7 @@ class ApiProvider {
|
||||||
try {
|
try {
|
||||||
final msg = server.ServerToClient.fromBuffer(msgBuffer);
|
final msg = server.ServerToClient.fromBuffer(msgBuffer);
|
||||||
if (msg.v0.hasResponse()) {
|
if (msg.v0.hasResponse()) {
|
||||||
|
removeFromRetransmissionBuffer(msg.v0.seq);
|
||||||
messagesV0[msg.v0.seq] = msg;
|
messagesV0[msg.v0.seq] = msg;
|
||||||
} else {
|
} else {
|
||||||
await handleServerMessage(msg);
|
await handleServerMessage(msg);
|
||||||
|
|
@ -160,7 +163,7 @@ class ApiProvider {
|
||||||
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
|
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
|
||||||
final startTime = DateTime.now();
|
final startTime = DateTime.now();
|
||||||
|
|
||||||
final timeout = Duration(seconds: 10);
|
final timeout = Duration(seconds: 20);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (messagesV0[seq] != null) {
|
if (messagesV0[seq] != null) {
|
||||||
|
|
@ -182,18 +185,51 @@ class ApiProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result> sendRequestSync(ClientToServer request,
|
Future<Map<String, dynamic>> getRetransmission() async {
|
||||||
{bool authenticated = true}) async {
|
final box = await getMediaStorage();
|
||||||
if (_channel == null) {
|
Map<String, dynamic>? retransmit = box.get("rawbytes-to-retransmit");
|
||||||
log.shout("sending request, but api is not connected.");
|
// Map<String, dynamic> retransmit = {};
|
||||||
if (!await connect()) {
|
// if (retransmitJson != null) {
|
||||||
return Result.error(ErrorCode.InternalError);
|
// try {
|
||||||
}
|
// retransmit = jsonDecode(retransmitJson);
|
||||||
}
|
// } catch (e) {
|
||||||
if (_channel == null) {
|
// Logger("api.dart").shout("Could not decode the rawbytes messages: $e");
|
||||||
return Result.error(ErrorCode.InternalError);
|
// await box.delete("rawbytes-to-retransmit");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return retransmit ?? {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future retransmitRawBytes() async {
|
||||||
|
var retransmit = await getRetransmission();
|
||||||
|
Logger("api_provider.dart")
|
||||||
|
.info("Retransmit: ${retransmit.keys.length} messages");
|
||||||
|
for (final seq in retransmit.keys) {
|
||||||
|
try {
|
||||||
|
_channel!.sink.add(base64Decode(retransmit[seq]));
|
||||||
|
} catch (e) {
|
||||||
|
Logger("api_provider.dart").shout("$e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future addToRetransmissionBuffer(Int64 seq, Uint8List bytes) async {
|
||||||
|
var retransmit = await getRetransmission();
|
||||||
|
retransmit[seq.toString()] = base64Encode(bytes);
|
||||||
|
final box = await getMediaStorage();
|
||||||
|
box.put("rawbytes-to-retransmit", retransmit);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future removeFromRetransmissionBuffer(Int64 seq) async {
|
||||||
|
var retransmit = await getRetransmission();
|
||||||
|
if (retransmit.isEmpty) return;
|
||||||
|
retransmit.remove(seq.toString());
|
||||||
|
final box = await getMediaStorage();
|
||||||
|
box.put("rawbytes-to-retransmit", retransmit);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Result> sendRequestSync(ClientToServer request,
|
||||||
|
{bool authenticated = true, bool ensureRetransmission = false}) async {
|
||||||
var seq = Int64(Random().nextInt(4294967296));
|
var seq = Int64(Random().nextInt(4294967296));
|
||||||
while (messagesV0.containsKey(seq)) {
|
while (messagesV0.containsKey(seq)) {
|
||||||
seq = Int64(Random().nextInt(4294967296));
|
seq = Int64(Random().nextInt(4294967296));
|
||||||
|
|
@ -201,6 +237,21 @@ class ApiProvider {
|
||||||
|
|
||||||
request.v0.seq = seq;
|
request.v0.seq = seq;
|
||||||
final requestBytes = request.writeToBuffer();
|
final requestBytes = request.writeToBuffer();
|
||||||
|
|
||||||
|
if (ensureRetransmission) {
|
||||||
|
addToRetransmissionBuffer(seq, requestBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_channel == null) {
|
||||||
|
log.shout("sending request, but api is not connected.");
|
||||||
|
if (!await connect()) {
|
||||||
|
return Result.error(ErrorCode.InternalError);
|
||||||
|
}
|
||||||
|
if (_channel == null) {
|
||||||
|
return Result.error(ErrorCode.InternalError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_channel!.sink.add(requestBytes);
|
_channel!.sink.add(requestBytes);
|
||||||
|
|
||||||
Result res = asResult(await _waitForResponse(seq));
|
Result res = asResult(await _waitForResponse(seq));
|
||||||
|
|
@ -372,6 +423,13 @@ class ApiProvider {
|
||||||
return await sendRequestSync(req);
|
return await sendRequestSync(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Result> downloadDone(List<int> token) async {
|
||||||
|
var get = ApplicationData_DownloadDone()..downloadToken = token;
|
||||||
|
var appData = ApplicationData()..downloaddone = get;
|
||||||
|
var req = createClientToServerFromApplicationData(appData);
|
||||||
|
return await sendRequestSync(req, ensureRetransmission: true);
|
||||||
|
}
|
||||||
|
|
||||||
Future<Result> getCurrentLocation() async {
|
Future<Result> getCurrentLocation() async {
|
||||||
var get = ApplicationData_GetLocation();
|
var get = ApplicationData_GetLocation();
|
||||||
var appData = ApplicationData()..getlocation = get;
|
var appData = ApplicationData()..getlocation = get;
|
||||||
|
|
@ -379,15 +437,6 @@ class ApiProvider {
|
||||||
return await sendRequestSync(req);
|
return await sendRequestSync(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result> triggerDownload(List<int> token, int offset) async {
|
|
||||||
var get = ApplicationData_DownloadData()
|
|
||||||
..downloadToken = token
|
|
||||||
..offset = offset;
|
|
||||||
var appData = ApplicationData()..downloaddata = get;
|
|
||||||
var req = createClientToServerFromApplicationData(appData);
|
|
||||||
return await sendRequestSync(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Result> uploadData(List<int> uploadToken, Uint8List data, int offset,
|
Future<Result> uploadData(List<int> uploadToken, Uint8List data, int offset,
|
||||||
List<int>? checksum) async {
|
List<int>? checksum) async {
|
||||||
var get = ApplicationData_UploadData()
|
var get = ApplicationData_UploadData()
|
||||||
|
|
|
||||||
|
|
@ -290,13 +290,17 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
if (isFront) {
|
if (isFront) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (controller == null) return;
|
||||||
|
if (!controller!.value.isInitialized) return;
|
||||||
|
|
||||||
scaleFactor = (baseScaleFactor + (basePanY - details.localPosition.dy) / 30)
|
scaleFactor = (baseScaleFactor + (basePanY - details.localPosition.dy) / 30)
|
||||||
.clamp(1, _maxAvailableZoom);
|
.clamp(1, _maxAvailableZoom);
|
||||||
|
|
||||||
await controller!.setZoomLevel(scaleFactor);
|
await controller!.setZoomLevel(scaleFactor);
|
||||||
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future pickImageFromGallery() async {
|
Future pickImageFromGallery() async {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue