From 76fcec05de35c6bf8e4f6fa414c2f7624f33a9f2 Mon Sep 17 00:00:00 2001 From: Nutcake Date: Wed, 17 May 2023 15:35:36 +0200 Subject: [PATCH] Implement chunked file upload and make uploaded files spawnable --- lib/apis/record_api.dart | 71 ++- lib/clients/api_client.dart | 3 + lib/models/records/asset_diff.dart | 8 +- lib/models/records/image_template.dart | 757 ++++++++++++++++++++++++ lib/widgets/messages/messages_list.dart | 15 +- pubspec.yaml | 2 +- 6 files changed, 818 insertions(+), 38 deletions(-) create mode 100644 lib/models/records/image_template.dart diff --git a/lib/apis/record_api.dart b/lib/apis/record_api.dart index 9d7ef38..1fb8e75 100644 --- a/lib/apis/record_api.dart +++ b/lib/apis/record_api.dart @@ -1,9 +1,14 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:math'; import 'dart:typed_data'; +import 'dart:ui'; +import 'package:collection/collection.dart'; import 'package:contacts_plus_plus/auxiliary.dart'; import 'package:contacts_plus_plus/models/message.dart'; +import 'package:contacts_plus_plus/models/records/image_template.dart'; import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/models/records/asset_upload_data.dart'; @@ -55,17 +60,19 @@ class RecordApi { ApiClient.checkResponse(response); } - static Future uploadAsset(ApiClient client, {required String filename, required NeosDBAsset asset, required Uint8List data}) async { - final request = http.MultipartRequest( - "POST", - ApiClient.buildFullUri("/users/${client.userId}/assets/${asset.hash}/chunks/0"), - )..files.add(http.MultipartFile.fromBytes("file", data, filename: filename, contentType: MediaType.parse("multipart/form-data"))) - ..headers.addAll(client.authorizationHeader); - final response = await request.send(); - final bodyBytes = await response.stream.toBytes(); - ApiClient.checkResponse(http.Response.bytes(bodyBytes, response.statusCode)); - final body = jsonDecode(bodyBytes.toString()); - return body; + static Future uploadAsset(ApiClient client, {required AssetUploadData uploadData, required String filename, required NeosDBAsset asset, required Uint8List data}) async { + for (int i = 0; i < uploadData.totalChunks; i++) { + final offset = i*uploadData.chunkSize; + final end = (i+1)*uploadData.chunkSize; + final request = http.MultipartRequest( + "POST", + ApiClient.buildFullUri("/users/${client.userId}/assets/${asset.hash}/chunks/$i"), + )..files.add(http.MultipartFile.fromBytes("file", data.getRange(offset, min(end, data.length)).toList(), filename: filename, contentType: MediaType.parse("multipart/form-data"))) + ..headers.addAll(client.authorizationHeader); + final response = await request.send(); + final bodyBytes = await response.stream.toBytes(); + ApiClient.checkResponse(http.Response.bytes(bodyBytes, response.statusCode)); + } } static Future finishUpload(ApiClient client, {required NeosDBAsset asset}) async { @@ -73,16 +80,21 @@ class RecordApi { ApiClient.checkResponse(response); } - static Future uploadFile(ApiClient client, {required File file, required String machineId}) async { - final data = await file.readAsBytes(); - final asset = NeosDBAsset.fromData(data); - final assetUri = "neosdb:///$machineId/${asset.hash}${extension(file.path)}"; + static Future uploadImage(ApiClient client, {required File image, required String machineId}) async { + final imageData = await image.readAsBytes(); + final imageImage = await decodeImageFromList(imageData); + final imageAsset = NeosDBAsset.fromData(imageData); + final imageNeosDbUri = "neosdb:///${imageAsset.hash}${extension(image.path)}"; + final objectJson = jsonEncode(ImageTemplate(imageUri: imageNeosDbUri, width: imageImage.width, height: imageImage.height).data); + final objectBytes = Uint8List.fromList(utf8.encode(objectJson)); + final objectAsset = NeosDBAsset.fromData(objectBytes); + final objectNeosDbUri = "neosdb:///${objectAsset.hash}.json"; final combinedRecordId = RecordId(id: Record.generateId(), ownerId: client.userId, isValid: true); - final filename = basenameWithoutExtension(file.path); + final filename = basenameWithoutExtension(image.path); final record = Record( id: combinedRecordId.id.toString(), combinedRecordId: combinedRecordId, - assetUri: assetUri, + assetUri: objectNeosDbUri, name: filename, tags: [ filename, @@ -90,12 +102,13 @@ class RecordApi { "message_id:${Message.generateId()}" ], recordType: RecordType.texture, - thumbnailUri: assetUri, + thumbnailUri: imageNeosDbUri, isPublic: false, isForPatreons: false, isListed: false, neosDBManifest: [ - asset, + imageAsset, + objectAsset, ], globalVersion: 0, localVersion: 1, @@ -109,7 +122,8 @@ class RecordApi { path: '', description: '', manifest: [ - assetUri + imageNeosDbUri, + objectNeosDbUri ], url: "neosrec:///${client.userId}/${combinedRecordId.id}", isValidOwnerId: true, @@ -128,14 +142,25 @@ class RecordApi { if (status.state != RecordPreprocessState.success) { throw "Record Preprocessing failed: ${status.failReason}"; } + AssetUploadData uploadData; + if ((status.resultDiffs.firstWhereOrNull((element) => element.hash == imageAsset.hash)?.isUploaded ?? false) == false) { + uploadData = await beginUploadAsset(client, asset: imageAsset); + if (uploadData.uploadState == UploadState.failed) { + throw "Asset upload failed: ${uploadData.uploadState.name}"; + } - final uploadData = await beginUploadAsset(client, asset: asset); + await uploadAsset(client, uploadData: uploadData, asset: imageAsset, data: imageData, filename: filename); + await finishUpload(client, asset: imageAsset); + } + + uploadData = await beginUploadAsset(client, asset: objectAsset); if (uploadData.uploadState == UploadState.failed) { throw "Asset upload failed: ${uploadData.uploadState.name}"; } - await uploadAsset(client, asset: asset, data: data, filename: filename); - await finishUpload(client, asset: asset); + await uploadAsset(client, uploadData: uploadData, asset: objectAsset, data: objectBytes, filename: filename); + await finishUpload(client, asset: objectAsset); + return record; } } \ No newline at end of file diff --git a/lib/clients/api_client.dart b/lib/clients/api_client.dart index 4bdfb1e..ac262ff 100644 --- a/lib/clients/api_client.dart +++ b/lib/clients/api_client.dart @@ -119,6 +119,9 @@ class ApiClient { static void checkResponse(http.Response response) { final error = "(${response.statusCode}${kDebugMode ? "|${response.body}" : ""})"; + if (response.statusCode >= 300) { + FlutterError.reportError(FlutterErrorDetails(exception: error)); + } if (response.statusCode == 429) { throw "Sorry, you are being rate limited. $error"; } diff --git a/lib/models/records/asset_diff.dart b/lib/models/records/asset_diff.dart index cd97d30..bb0a2ce 100644 --- a/lib/models/records/asset_diff.dart +++ b/lib/models/records/asset_diff.dart @@ -1,11 +1,11 @@ -class AssetDiff { - final String hash; - final int bytes; +import 'package:contacts_plus_plus/models/records/neos_db_asset.dart'; + +class AssetDiff extends NeosDBAsset{ final Diff state; final bool isUploaded; - const AssetDiff({required this.hash, required this.bytes, required this.state, required this.isUploaded}); + const AssetDiff({required hash, required bytes, required this.state, required this.isUploaded}) : super(hash: hash, bytes: bytes); factory AssetDiff.fromMap(Map map) { return AssetDiff( diff --git a/lib/models/records/image_template.dart b/lib/models/records/image_template.dart new file mode 100644 index 0000000..de23730 --- /dev/null +++ b/lib/models/records/image_template.dart @@ -0,0 +1,757 @@ +import 'dart:ui'; + +import 'package:uuid/uuid.dart'; + +class ImageTemplate { + late final Map data; + + ImageTemplate({required String imageUri, required int width, required int height}) { + final texture2dUid = const Uuid().v4(); + final quadMeshUid = const Uuid().v4(); + final quadMeshSizeUid = const Uuid().v4(); + final materialId = const Uuid().v4(); + final boxColliderSizeUid = const Uuid().v4(); + data = { + "Object": { + "ID": const Uuid().v4(), + "Components": { + "ID": const Uuid().v4(), + "Data": [ + { + "Type": "FrooxEngine.Grabbable", + "Data": { + "ID": const Uuid().v4(), + "persistent-ID": const Uuid().v4(), + "UpdateOrder": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "Enabled": { + "ID": const Uuid().v4(), + "Data": true + }, + "ReparentOnRelease": { + "ID": const Uuid().v4(), + "Data": true + }, + "PreserveUserSpace": { + "ID": const Uuid().v4(), + "Data": true + }, + "DestroyOnRelease": { + "ID": const Uuid().v4(), + "Data": false + }, + "GrabPriority": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "GrabPriorityWhenGrabbed": { + "ID": const Uuid().v4(), + "Data": null + }, + "CustomCanGrabCheck": { + "ID": const Uuid().v4(), + "Data": { + "Target": null + } + }, + "EditModeOnly": { + "ID": const Uuid().v4(), + "Data": false + }, + "AllowSteal": { + "ID": const Uuid().v4(), + "Data": false + }, + "DropOnDisable": { + "ID": const Uuid().v4(), + "Data": true + }, + "ActiveUserFilter": { + "ID": const Uuid().v4(), + "Data": "Disabled" + }, + "OnlyUsers": { + "ID": const Uuid().v4(), + "Data": [] + }, + "Scalable": { + "ID": const Uuid().v4(), + "Data": true + }, + "Receivable": { + "ID": const Uuid().v4(), + "Data": true + }, + "AllowOnlyPhysicalGrab": { + "ID": const Uuid().v4(), + "Data": false + }, + "_grabber": { + "ID": const Uuid().v4(), + "Data": null + }, + "_lastParent": { + "ID": const Uuid().v4(), + "Data": null + }, + "_lastParentIsUserSpace": { + "ID": const Uuid().v4(), + "Data": true + }, + "__legacyActiveUserRootOnly-ID": const Uuid().v4() + } + }, + { + "Type": "FrooxEngine.StaticTexture2D", + "Data": { + "ID": texture2dUid, + "persistent-ID": const Uuid().v4(), + "UpdateOrder": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "Enabled": { + "ID": const Uuid().v4(), + "Data": true + }, + "URL": { + "ID": const Uuid().v4(), + "Data": "@$imageUri" + }, + "FilterMode": { + "ID": const Uuid().v4(), + "Data": "Anisotropic" + }, + "AnisotropicLevel": { + "ID": const Uuid().v4(), + "Data": 16 + }, + "Uncompressed": { + "ID": const Uuid().v4(), + "Data": false + }, + "DirectLoad": { + "ID": const Uuid().v4(), + "Data": false + }, + "ForceExactVariant": { + "ID": const Uuid().v4(), + "Data": false + }, + "PreferredFormat": { + "ID": const Uuid().v4(), + "Data": null + }, + "MipMapBias": { + "ID": const Uuid().v4(), + "Data": 0.0 + }, + "IsNormalMap": { + "ID": const Uuid().v4(), + "Data": false + }, + "WrapModeU": { + "ID": const Uuid().v4(), + "Data": "Repeat" + }, + "WrapModeV": { + "ID": const Uuid().v4(), + "Data": "Repeat" + }, + "PowerOfTwoAlignThreshold": { + "ID": const Uuid().v4(), + "Data": 0.05 + }, + "CrunchCompressed": { + "ID": const Uuid().v4(), + "Data": true + }, + "MaxSize": { + "ID": const Uuid().v4(), + "Data": null + }, + "MipMaps": { + "ID": const Uuid().v4(), + "Data": true + }, + "MipMapFilter": { + "ID": const Uuid().v4(), + "Data": "Box" + }, + "Readable": { + "ID": const Uuid().v4(), + "Data": false + } + } + }, + { + "Type": "FrooxEngine.ItemTextureThumbnailSource", + "Data": { + "ID": const Uuid().v4(), + "persistent-ID": const Uuid().v4(), + "UpdateOrder": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "Enabled": { + "ID": const Uuid().v4(), + "Data": true + }, + "Texture": { + "ID": const Uuid().v4(), + "Data": texture2dUid + }, + "Crop": { + "ID": const Uuid().v4(), + "Data": null + } + } + }, + { + "Type": "FrooxEngine.SnapPlane", + "Data": { + "ID": const Uuid().v4(), + "persistent-ID": const Uuid().v4(), + "UpdateOrder": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "Enabled": { + "ID": const Uuid().v4(), + "Data": true + }, + "Normal": { + "ID": const Uuid().v4(), + "Data": [ + 0.0, + 0.0, + 1.0 + ] + }, + "SnapParent": { + "ID": const Uuid().v4(), + "Data": null + } + } + }, + { + "Type": "FrooxEngine.ReferenceProxy", + "Data": { + "ID": const Uuid().v4(), + "persistent-ID": const Uuid().v4(), + "UpdateOrder": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "Enabled": { + "ID": const Uuid().v4(), + "Data": true + }, + "Reference": { + "ID": const Uuid().v4(), + "Data": texture2dUid + }, + "SpawnInstanceOnTrigger": { + "ID": const Uuid().v4(), + "Data": false + } + } + }, + { + "Type": "FrooxEngine.AssetProxy`1[[FrooxEngine.Texture2D, FrooxEngine, Version=2022.1.28.1335, Culture=neutral, PublicKeyToken=null]]", + "Data": { + "ID": const Uuid().v4(), + "persistent-ID": const Uuid().v4(), + "UpdateOrder": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "Enabled": { + "ID": const Uuid().v4(), + "Data": true + }, + "AssetReference": { + "ID": const Uuid().v4(), + "Data": texture2dUid + } + } + }, + { + "Type": "FrooxEngine.UnlitMaterial", + "Data": { + "ID": materialId, + "persistent-ID": const Uuid().v4(), + "UpdateOrder": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "Enabled": { + "ID": const Uuid().v4(), + "Data": true + }, + "HighPriorityIntegration": { + "ID": const Uuid().v4(), + "Data": false + }, + "TintColor": { + "ID": const Uuid().v4(), + "Data": [ + 1.0, + 1.0, + 1.0, + 1.0 + ] + }, + "Texture": { + "ID": const Uuid().v4(), + "Data": texture2dUid + }, + "TextureScale": { + "ID": const Uuid().v4(), + "Data": [ + 1.0, + 1.0 + ] + }, + "TextureOffset": { + "ID": const Uuid().v4(), + "Data": [ + 0.0, + 0.0 + ] + }, + "MaskTexture": { + "ID": const Uuid().v4(), + "Data": null + }, + "MaskScale": { + "ID": const Uuid().v4(), + "Data": [ + 1.0, + 1.0 + ] + }, + "MaskOffset": { + "ID": const Uuid().v4(), + "Data": [ + 0.0, + 0.0 + ] + }, + "MaskMode": { + "ID": const Uuid().v4(), + "Data": "MultiplyAlpha" + }, + "BlendMode": { + "ID": const Uuid().v4(), + "Data": "Alpha" + }, + "AlphaCutoff": { + "ID": const Uuid().v4(), + "Data": 0.5 + }, + "UseVertexColors": { + "ID": const Uuid().v4(), + "Data": true + }, + "Sidedness": { + "ID": const Uuid().v4(), + "Data": "Double" + }, + "ZWrite": { + "ID": const Uuid().v4(), + "Data": "Auto" + }, + "OffsetTexture": { + "ID": const Uuid().v4(), + "Data": null + }, + "OffsetMagnitude": { + "ID": const Uuid().v4(), + "Data": [ + 0.0, + 0.0 + ] + }, + "OffsetTextureScale": { + "ID": const Uuid().v4(), + "Data": [ + 1.0, + 1.0 + ] + }, + "OffsetTextureOffset": { + "ID": const Uuid().v4(), + "Data": [ + 0.0, + 0.0 + ] + }, + "PolarUVmapping": { + "ID": const Uuid().v4(), + "Data": false + }, + "PolarPower": { + "ID": const Uuid().v4(), + "Data": 1.0 + }, + "StereoTextureTransform": { + "ID": const Uuid().v4(), + "Data": false + }, + "RightEyeTextureScale": { + "ID": const Uuid().v4(), + "Data": [ + 1.0, + 1.0 + ] + }, + "RightEyeTextureOffset": { + "ID": const Uuid().v4(), + "Data": [ + 0.0, + 0.0 + ] + }, + "DecodeAsNormalMap": { + "ID": const Uuid().v4(), + "Data": false + }, + "UseBillboardGeometry": { + "ID": const Uuid().v4(), + "Data": false + }, + "UsePerBillboardScale": { + "ID": const Uuid().v4(), + "Data": false + }, + "UsePerBillboardRotation": { + "ID": const Uuid().v4(), + "Data": false + }, + "UsePerBillboardUV": { + "ID": const Uuid().v4(), + "Data": false + }, + "BillboardSize": { + "ID": const Uuid().v4(), + "Data": [ + 0.005, + 0.005 + ] + }, + "OffsetFactor": { + "ID": const Uuid().v4(), + "Data": 0.0 + }, + "OffsetUnits": { + "ID": const Uuid().v4(), + "Data": 0.0 + }, + "RenderQueue": { + "ID": const Uuid().v4(), + "Data": -1 + }, + "_unlit-ID": const Uuid().v4(), + "_unlitBillboard-ID": const Uuid().v4() + } + }, + { + "Type": "FrooxEngine.QuadMesh", + "Data": { + "ID": quadMeshUid, + "persistent-ID": const Uuid().v4(), + "UpdateOrder": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "Enabled": { + "ID": const Uuid().v4(), + "Data": true + }, + "HighPriorityIntegration": { + "ID": const Uuid().v4(), + "Data": false + }, + "OverrideBoundingBox": { + "ID": const Uuid().v4(), + "Data": false + }, + "OverridenBoundingBox": { + "ID": const Uuid().v4(), + "Data": { + "Min": [ + 0.0, + 0.0, + 0.0 + ], + "Max": [ + 0.0, + 0.0, + 0.0 + ] + } + }, + "Rotation": { + "ID": const Uuid().v4(), + "Data": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "Size": { + "ID": quadMeshSizeUid, + "Data": [ + 1, + height/width + ] + }, + "UVScale": { + "ID": const Uuid().v4(), + "Data": [ + 1.0, + 1.0 + ] + }, + "ScaleUVWithSize": { + "ID": const Uuid().v4(), + "Data": false + }, + "UVOffset": { + "ID": const Uuid().v4(), + "Data": [ + 0.0, + 0.0 + ] + }, + "DualSided": { + "ID": const Uuid().v4(), + "Data": false + }, + "UseVertexColors": { + "ID": const Uuid().v4(), + "Data": true + }, + "UpperLeftColor": { + "ID": const Uuid().v4(), + "Data": [ + 1.0, + 1.0, + 1.0, + 1.0 + ] + }, + "LowerLeftColor": { + "ID": const Uuid().v4(), + "Data": [ + 1.0, + 1.0, + 1.0, + 1.0 + ] + }, + "LowerRightColor": { + "ID": const Uuid().v4(), + "Data": [ + 1.0, + 1.0, + 1.0, + 1.0 + ] + }, + "UpperRightColor": { + "ID": const Uuid().v4(), + "Data": [ + 1.0, + 1.0, + 1.0, + 1.0 + ] + } + } + }, + { + "Type": "FrooxEngine.MeshRenderer", + "Data": { + "ID": const Uuid().v4(), + "persistent-ID": const Uuid().v4(), + "UpdateOrder": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "Enabled": { + "ID": const Uuid().v4(), + "Data": true + }, + "Mesh": { + "ID": const Uuid().v4(), + "Data": quadMeshUid + }, + "Materials": { + "ID": const Uuid().v4(), + "Data": [ + { + "ID": const Uuid().v4(), + "Data": materialId + } + ] + }, + "MaterialPropertyBlocks": { + "ID": const Uuid().v4(), + "Data": [] + }, + "ShadowCastMode": { + "ID": const Uuid().v4(), + "Data": "On" + }, + "MotionVectorMode": { + "ID": const Uuid().v4(), + "Data": "Object" + }, + "SortingOrder": { + "ID": const Uuid().v4(), + "Data": 0 + } + } + }, + { + "Type": "FrooxEngine.BoxCollider", + "Data": { + "ID": const Uuid().v4(), + "persistent-ID": const Uuid().v4(), + "UpdateOrder": { + "ID": const Uuid().v4(), + "Data": 1000000 + }, + "Enabled": { + "ID": const Uuid().v4(), + "Data": true + }, + "Offset": { + "ID": const Uuid().v4(), + "Data": [ + 0.0, + 0.0, + 0.0 + ] + }, + "Type": { + "ID": const Uuid().v4(), + "Data": "NoCollision" + }, + "Mass": { + "ID": const Uuid().v4(), + "Data": 1.0 + }, + "CharacterCollider": { + "ID": const Uuid().v4(), + "Data": false + }, + "IgnoreRaycasts": { + "ID": const Uuid().v4(), + "Data": false + }, + "Size": { + "ID": boxColliderSizeUid, + "Data": [ + 0.7071067, + 0.7071067, + 0.0 + ] + } + } + }, + { + "Type": "FrooxEngine.Float2ToFloat3SwizzleDriver", + "Data": { + "ID": const Uuid().v4(), + "persistent-ID": const Uuid().v4(), + "UpdateOrder": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "Enabled": { + "ID": const Uuid().v4(), + "Data": true + }, + "Source": { + "ID": const Uuid().v4(), + "Data": quadMeshSizeUid + }, + "Target": { + "ID": const Uuid().v4(), + "Data": boxColliderSizeUid + }, + "X": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "Y": { + "ID": const Uuid().v4(), + "Data": 1 + }, + "Z": { + "ID": const Uuid().v4(), + "Data": -1 + } + } + } + ] + }, + "Name": { + "ID": const Uuid().v4(), + "Data": "alice" + }, + "Tag": { + "ID": const Uuid().v4(), + "Data": null + }, + "Active": { + "ID": const Uuid().v4(), + "Data": true + }, + "Persistent-ID": const Uuid().v4(), + "Position": { + "ID": const Uuid().v4(), + "Data": [ + 0.8303015, + 1.815294, + 0.494639724 + ] + }, + "Rotation": { + "ID": const Uuid().v4(), + "Data": [ + 1.05315749E-07, + 0.0222634021, + -1.08297385E-07, + 0.999752164 + ] + }, + "Scale": { + "ID": const Uuid().v4(), + "Data": [ + 0.9999994, + 0.999999464, + 0.99999994 + ] + }, + "OrderOffset": { + "ID": const Uuid().v4(), + "Data": 0 + }, + "ParentReference": const Uuid().v4(), + "Children": [] + }, + "TypeVersions": { + "FrooxEngine.Grabbable": 2, + "FrooxEngine.QuadMesh": 1, + "FrooxEngine.BoxCollider": 1 + } + }; + } +} \ No newline at end of file diff --git a/lib/widgets/messages/messages_list.dart b/lib/widgets/messages/messages_list.dart index bd6672c..81a21fd 100644 --- a/lib/widgets/messages/messages_list.dart +++ b/lib/widgets/messages/messages_list.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:io'; import 'package:contacts_plus_plus/apis/record_api.dart'; -import 'package:contacts_plus_plus/auxiliary.dart'; import 'package:contacts_plus_plus/client_holder.dart'; import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/clients/messaging_client.dart'; @@ -80,6 +79,7 @@ class _MessagesListState extends State with SingleTickerProviderSt } Future sendTextMessage(ScaffoldMessengerState scaffoldMessenger, ApiClient client, MessagingClient mClient, String content) async { + if (content.isEmpty) return; setState(() { _isSending = true; }); @@ -114,16 +114,11 @@ class _MessagesListState extends State with SingleTickerProviderSt _isSending = true; }); try { - var record = await RecordApi.uploadFile( + final record = await RecordApi.uploadImage( client, - file: file, + image: file, machineId: machineId, ); - final newUri = Aux.neosDbToHttp(record.assetUri); - record = record.copyWith( - assetUri: newUri, - thumbnailUri: newUri, - ); final message = Message( id: Message.generateId(), @@ -309,7 +304,7 @@ class _MessagesListState extends State with SingleTickerProviderSt duration: const Duration(milliseconds: 250), child: Row( children: [ - /*IconButton( + IconButton( onPressed: _hasText ? null : _loadedFile == null ? () async { //final machineId = ClientHolder.of(context).settingsClient.currentSettings.machineId.valueOrDefault; final result = await FilePicker.platform.pickFiles(type: FileType.image); @@ -321,7 +316,7 @@ class _MessagesListState extends State with SingleTickerProviderSt } } : () => setState(() => _loadedFile = null), icon: _loadedFile == null ? const Icon(Icons.attach_file) : const Icon(Icons.close), - ),*/ + ), Expanded( child: Padding( padding: const EdgeInsets.all(8), diff --git a/pubspec.yaml b/pubspec.yaml index e681979..fce28f9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.2.3+1 +version: 1.3.0+1 environment: sdk: '>=3.0.0'