Implement chunked file upload and make uploaded files spawnable

This commit is contained in:
Nutcake 2023-05-17 15:35:36 +02:00
parent 362f0cef09
commit 76fcec05de
6 changed files with 818 additions and 38 deletions

View file

@ -1,9 +1,14 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui';
import 'package:collection/collection.dart';
import 'package:contacts_plus_plus/auxiliary.dart'; import 'package:contacts_plus_plus/auxiliary.dart';
import 'package:contacts_plus_plus/models/message.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:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/clients/api_client.dart';
import 'package:contacts_plus_plus/models/records/asset_upload_data.dart'; import 'package:contacts_plus_plus/models/records/asset_upload_data.dart';
@ -55,17 +60,19 @@ class RecordApi {
ApiClient.checkResponse(response); ApiClient.checkResponse(response);
} }
static Future<dynamic> uploadAsset(ApiClient client, {required String filename, required NeosDBAsset asset, required Uint8List data}) async { static Future<void> uploadAsset(ApiClient client, {required AssetUploadData uploadData, required String filename, required NeosDBAsset asset, required Uint8List data}) async {
final request = http.MultipartRequest( for (int i = 0; i < uploadData.totalChunks; i++) {
"POST", final offset = i*uploadData.chunkSize;
ApiClient.buildFullUri("/users/${client.userId}/assets/${asset.hash}/chunks/0"), final end = (i+1)*uploadData.chunkSize;
)..files.add(http.MultipartFile.fromBytes("file", data, filename: filename, contentType: MediaType.parse("multipart/form-data"))) final request = http.MultipartRequest(
..headers.addAll(client.authorizationHeader); "POST",
final response = await request.send(); ApiClient.buildFullUri("/users/${client.userId}/assets/${asset.hash}/chunks/$i"),
final bodyBytes = await response.stream.toBytes(); )..files.add(http.MultipartFile.fromBytes("file", data.getRange(offset, min(end, data.length)).toList(), filename: filename, contentType: MediaType.parse("multipart/form-data")))
ApiClient.checkResponse(http.Response.bytes(bodyBytes, response.statusCode)); ..headers.addAll(client.authorizationHeader);
final body = jsonDecode(bodyBytes.toString()); final response = await request.send();
return body; final bodyBytes = await response.stream.toBytes();
ApiClient.checkResponse(http.Response.bytes(bodyBytes, response.statusCode));
}
} }
static Future<void> finishUpload(ApiClient client, {required NeosDBAsset asset}) async { static Future<void> finishUpload(ApiClient client, {required NeosDBAsset asset}) async {
@ -73,16 +80,21 @@ class RecordApi {
ApiClient.checkResponse(response); ApiClient.checkResponse(response);
} }
static Future<Record> uploadFile(ApiClient client, {required File file, required String machineId}) async { static Future<Record> uploadImage(ApiClient client, {required File image, required String machineId}) async {
final data = await file.readAsBytes(); final imageData = await image.readAsBytes();
final asset = NeosDBAsset.fromData(data); final imageImage = await decodeImageFromList(imageData);
final assetUri = "neosdb:///$machineId/${asset.hash}${extension(file.path)}"; 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 combinedRecordId = RecordId(id: Record.generateId(), ownerId: client.userId, isValid: true);
final filename = basenameWithoutExtension(file.path); final filename = basenameWithoutExtension(image.path);
final record = Record( final record = Record(
id: combinedRecordId.id.toString(), id: combinedRecordId.id.toString(),
combinedRecordId: combinedRecordId, combinedRecordId: combinedRecordId,
assetUri: assetUri, assetUri: objectNeosDbUri,
name: filename, name: filename,
tags: [ tags: [
filename, filename,
@ -90,12 +102,13 @@ class RecordApi {
"message_id:${Message.generateId()}" "message_id:${Message.generateId()}"
], ],
recordType: RecordType.texture, recordType: RecordType.texture,
thumbnailUri: assetUri, thumbnailUri: imageNeosDbUri,
isPublic: false, isPublic: false,
isForPatreons: false, isForPatreons: false,
isListed: false, isListed: false,
neosDBManifest: [ neosDBManifest: [
asset, imageAsset,
objectAsset,
], ],
globalVersion: 0, globalVersion: 0,
localVersion: 1, localVersion: 1,
@ -109,7 +122,8 @@ class RecordApi {
path: '', path: '',
description: '', description: '',
manifest: [ manifest: [
assetUri imageNeosDbUri,
objectNeosDbUri
], ],
url: "neosrec:///${client.userId}/${combinedRecordId.id}", url: "neosrec:///${client.userId}/${combinedRecordId.id}",
isValidOwnerId: true, isValidOwnerId: true,
@ -128,14 +142,25 @@ class RecordApi {
if (status.state != RecordPreprocessState.success) { if (status.state != RecordPreprocessState.success) {
throw "Record Preprocessing failed: ${status.failReason}"; 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) { if (uploadData.uploadState == UploadState.failed) {
throw "Asset upload failed: ${uploadData.uploadState.name}"; throw "Asset upload failed: ${uploadData.uploadState.name}";
} }
await uploadAsset(client, asset: asset, data: data, filename: filename); await uploadAsset(client, uploadData: uploadData, asset: objectAsset, data: objectBytes, filename: filename);
await finishUpload(client, asset: asset); await finishUpload(client, asset: objectAsset);
return record; return record;
} }
} }

View file

@ -119,6 +119,9 @@ class ApiClient {
static void checkResponse(http.Response response) { static void checkResponse(http.Response response) {
final error = "(${response.statusCode}${kDebugMode ? "|${response.body}" : ""})"; final error = "(${response.statusCode}${kDebugMode ? "|${response.body}" : ""})";
if (response.statusCode >= 300) {
FlutterError.reportError(FlutterErrorDetails(exception: error));
}
if (response.statusCode == 429) { if (response.statusCode == 429) {
throw "Sorry, you are being rate limited. $error"; throw "Sorry, you are being rate limited. $error";
} }

View file

@ -1,11 +1,11 @@
class AssetDiff { import 'package:contacts_plus_plus/models/records/neos_db_asset.dart';
final String hash;
final int bytes; class AssetDiff extends NeosDBAsset{
final Diff state; final Diff state;
final bool isUploaded; 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) { factory AssetDiff.fromMap(Map map) {
return AssetDiff( return AssetDiff(

View file

@ -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
}
};
}
}

View file

@ -2,7 +2,6 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:contacts_plus_plus/apis/record_api.dart'; 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/client_holder.dart';
import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/clients/api_client.dart';
import 'package:contacts_plus_plus/clients/messaging_client.dart'; import 'package:contacts_plus_plus/clients/messaging_client.dart';
@ -80,6 +79,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
} }
Future<void> sendTextMessage(ScaffoldMessengerState scaffoldMessenger, ApiClient client, MessagingClient mClient, String content) async { Future<void> sendTextMessage(ScaffoldMessengerState scaffoldMessenger, ApiClient client, MessagingClient mClient, String content) async {
if (content.isEmpty) return;
setState(() { setState(() {
_isSending = true; _isSending = true;
}); });
@ -114,16 +114,11 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
_isSending = true; _isSending = true;
}); });
try { try {
var record = await RecordApi.uploadFile( final record = await RecordApi.uploadImage(
client, client,
file: file, image: file,
machineId: machineId, machineId: machineId,
); );
final newUri = Aux.neosDbToHttp(record.assetUri);
record = record.copyWith(
assetUri: newUri,
thumbnailUri: newUri,
);
final message = Message( final message = Message(
id: Message.generateId(), id: Message.generateId(),
@ -309,7 +304,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
duration: const Duration(milliseconds: 250), duration: const Duration(milliseconds: 250),
child: Row( child: Row(
children: [ children: [
/*IconButton( IconButton(
onPressed: _hasText ? null : _loadedFile == null ? () async { onPressed: _hasText ? null : _loadedFile == null ? () async {
//final machineId = ClientHolder.of(context).settingsClient.currentSettings.machineId.valueOrDefault; //final machineId = ClientHolder.of(context).settingsClient.currentSettings.machineId.valueOrDefault;
final result = await FilePicker.platform.pickFiles(type: FileType.image); final result = await FilePicker.platform.pickFiles(type: FileType.image);
@ -321,7 +316,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
} }
} : () => setState(() => _loadedFile = null), } : () => setState(() => _loadedFile = null),
icon: _loadedFile == null ? const Icon(Icons.attach_file) : const Icon(Icons.close), icon: _loadedFile == null ? const Icon(Icons.attach_file) : const Icon(Icons.close),
),*/ ),
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),

View file

@ -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 # 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 # 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. # 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: environment:
sdk: '>=3.0.0' sdk: '>=3.0.0'