Add progress indicator for file upload

This commit is contained in:
Nutcake 2023-05-18 10:52:32 +02:00
parent 717cdb5064
commit 3c4a4fb80b
2 changed files with 55 additions and 22 deletions

View file

@ -72,8 +72,9 @@ class RecordApi {
}
static Future<void> uploadAsset(ApiClient client,
{required AssetUploadData uploadData, required String filename, required NeosDBAsset asset, required Uint8List data}) async {
{required AssetUploadData uploadData, required String filename, required NeosDBAsset asset, required Uint8List data, void Function(double number)? progressCallback}) async {
for (int i = 0; i < uploadData.totalChunks; i++) {
progressCallback?.call(i/uploadData.totalChunks);
final offset = i * uploadData.chunkSize;
final end = (i + 1) * uploadData.chunkSize;
final request = http.MultipartRequest(
@ -87,6 +88,7 @@ class RecordApi {
final response = await request.send();
final bodyBytes = await response.stream.toBytes();
ApiClient.checkResponse(http.Response.bytes(bodyBytes, response.statusCode));
progressCallback?.call(1);
}
}
@ -95,18 +97,30 @@ class RecordApi {
ApiClient.checkResponse(response);
}
static Future<void> uploadAssets(ApiClient client, {required List<AssetDigest> assets}) async {
for (final entry in assets) {
static Future<void> uploadAssets(ApiClient client, {required List<AssetDigest> assets, void Function(double progress)? progressCallback}) async {
progressCallback?.call(0);
for (int i = 0; i < assets.length; i++) {
final totalProgress = i/assets.length;
progressCallback?.call(totalProgress);
final entry = assets[i];
final uploadData = await beginUploadAsset(client, asset: entry.asset);
if (uploadData.uploadState == UploadState.failed) {
throw "Asset upload failed: ${uploadData.uploadState.name}";
}
await uploadAsset(client, uploadData: uploadData, asset: entry.asset, data: entry.data, filename: entry.name);
await uploadAsset(client,
uploadData: uploadData,
asset: entry.asset,
data: entry.data,
filename: entry.name,
progressCallback: (progress) => progressCallback?.call(totalProgress + progress * 1/assets.length),
);
await finishUpload(client, asset: entry.asset);
}
progressCallback?.call(1);
}
static Future<Record> uploadImage(ApiClient client, {required File image, required String machineId}) async {
static Future<Record> uploadImage(ApiClient client, {required File image, required String machineId, void Function(double progress)? progressCallback}) async {
progressCallback?.call(0);
final imageDigest = await AssetDigest.fromData(await image.readAsBytes(), basename(image.path));
final imageData = await decodeImageFromList(imageDigest.data);
@ -128,12 +142,16 @@ class RecordApi {
thumbnailUri: imageDigest.dbUri,
digests: digests,
);
progressCallback?.call(.1);
final status = await tryPreprocessRecord(client, record: record);
final toUpload = status.resultDiffs.whereNot((element) => element.isUploaded);
progressCallback?.call(.2);
await uploadAssets(
client, assets: digests.where((digest) => toUpload.any((diff) => digest.asset.hash == diff.hash)).toList());
client,
assets: digests.where((digest) => toUpload.any((diff) => digest.asset.hash == diff.hash)).toList(),
progressCallback: (progress) => progressCallback?.call(.2 + progress * .6));
progressCallback?.call(1);
return record;
}
}

View file

@ -35,6 +35,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
bool _hasText = false;
bool _isSending = false;
bool _attachmentPickerOpen = false;
double _sendProgress = 0;
bool _showBottomBarShadow = false;
bool _showSessionListScrollChevron = false;
@ -84,8 +85,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
});
}
Future<void> sendTextMessage(ApiClient client, MessagingClient mClient,
String content) async {
Future<void> sendTextMessage(ApiClient client, MessagingClient mClient, String content) async {
if (content.isEmpty) return;
final message = Message(
id: Message.generateId(),
@ -97,13 +97,15 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
);
mClient.sendMessage(message);
_messageTextController.clear();
_hasText = false;
}
Future<void> sendImageMessage(ApiClient client, MessagingClient mClient, File file, machineId) async {
Future<void> sendImageMessage(ApiClient client, MessagingClient mClient, File file, String machineId, void Function(double progress) progressCallback) async {
final record = await RecordApi.uploadImage(
client,
image: file,
machineId: machineId,
progressCallback: progressCallback,
);
final message = Message(
id: Message.generateId(),
@ -115,6 +117,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
);
mClient.sendMessage(message);
_messageTextController.clear();
_hasText = false;
}
@override
@ -287,7 +290,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
key: const ValueKey("attachment-picker"),
children: [
TextButton.icon(
onPressed: () async {
onPressed: _isSending ? null : () async {
final result = await FilePicker.platform.pickFiles(type: FileType.image);
if (result != null && result.files.single.path != null) {
setState(() {
@ -298,7 +301,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
icon: const Icon(Icons.image),
label: const Text("Gallery"),
),
TextButton.icon(onPressed: (){}, icon: const Icon(Icons.camera), label: const Text("Camera"),),
TextButton.icon(onPressed: _isSending ? null : (){}, icon: const Icon(Icons.camera), label: const Text("Camera"),),
],
),
(false, []) => null,
@ -309,11 +312,11 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _loadedFiles.map((e) => TextButton.icon(onPressed: (){}, label: Text(basename(e.path)), icon: const Icon(Icons.attach_file))).toList()
children: _loadedFiles.map((e) => TextButton.icon(onPressed: _isSending ? null : (){}, label: Text(basename(e.path)), icon: const Icon(Icons.attach_file))).toList()
),
),
),
IconButton(onPressed: () async {
IconButton(onPressed: _isSending ? null : () async {
final result = await FilePicker.platform.pickFiles(type: FileType.image);
if (result != null && result.files.single.path != null) {
setState(() {
@ -321,7 +324,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
});
}
}, icon: const Icon(Icons.image)),
IconButton(onPressed: () {}, icon: const Icon(Icons.camera)),
IconButton(onPressed: _isSending ? null : () {}, icon: const Icon(Icons.camera)),
],
)
},
@ -331,9 +334,9 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
),
),
if (_isSending && _loadedFiles.isNotEmpty)
const Align(
Align(
alignment: Alignment.bottomCenter,
child: LinearProgressIndicator(),
child: LinearProgressIndicator(value: _sendProgress),
),
],
),
@ -371,7 +374,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
child: !_attachmentPickerOpen ?
IconButton(
key: const ValueKey("add-attachment-icon"),
onPressed: () async {
onPressed:_isSending ? null : () {
setState(() {
_attachmentPickerOpen = true;
});
@ -380,7 +383,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
) :
IconButton(
key: const ValueKey("remove-attachment-icon"),
onPressed: () {
onPressed: _isSending ? null : () {
setState(() {
_loadedFiles.clear();
_attachmentPickerOpen = false;
@ -393,7 +396,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
child: Padding(
padding: const EdgeInsets.all(8),
child: TextField(
enabled: cache != null && cache.error == null,
enabled: cache != null && cache.error == null && !_isSending,
autocorrect: true,
controller: _messageTextController,
maxLines: 4,
@ -436,17 +439,29 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
final sMsgnr = ScaffoldMessenger.of(context);
setState(() {
_isSending = true;
_sendProgress = 0;
});
try {
for (final file in _loadedFiles) {
for (int i = 0; i < _loadedFiles.length; i++) {
final totalProgress = i/_loadedFiles.length;
final file = _loadedFiles[i];
await sendImageMessage(apiClient, mClient, file, ClientHolder
.of(context)
.settingsClient
.currentSettings
.machineId
.valueOrDefault);
.valueOrDefault,
(progress) =>
setState(() {
_sendProgress = totalProgress + progress * 1/_loadedFiles.length;
}),
);
}
setState(() {
_sendProgress = 1;
});
if (_hasText) {
await sendTextMessage(apiClient, mClient, _messageTextController.text);
}