diff --git a/lib/apis/record_api.dart b/lib/apis/record_api.dart index 4de1957..84c9825 100644 --- a/lib/apis/record_api.dart +++ b/lib/apis/record_api.dart @@ -1,21 +1,24 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:math'; import 'dart:typed_data'; + +import 'package:bson/bson.dart'; import 'package:collection/collection.dart'; -import 'package:recon/models/records/asset_digest.dart'; -import 'package:recon/models/records/json_template.dart'; import 'package:http/http.dart' as http; -import 'package:flutter/material.dart'; - +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:recon/clients/api_client.dart'; +import 'package:recon/models/records/asset_chunk.dart'; +import 'package:recon/models/records/asset_diff.dart'; +import 'package:recon/models/records/asset_manifest.dart'; import 'package:recon/models/records/asset_upload_data.dart'; -import 'package:recon/models/records/resonite_db_asset.dart'; +import 'package:recon/models/records/cloudflare_chunk_result.dart'; +import 'package:recon/models/records/json_template.dart'; import 'package:recon/models/records/preprocess_status.dart'; import 'package:recon/models/records/record.dart'; -import 'package:http_parser/http_parser.dart'; -import 'package:path/path.dart'; import 'package:recon/models/records/search_sort.dart'; +import 'package:image/image.dart' as img; class RecordApi { static Future getUserRecord(ApiClient client, {required String recordId, String? user}) async { @@ -89,183 +92,187 @@ class RecordApi { return PreprocessStatus.fromMap(body); } - static Future tryPreprocessRecord(ApiClient client, {required Record record}) async { - var status = await preprocessRecord(client, record: record); - while (status.state == RecordPreprocessState.preprocessing) { - await Future.delayed(const Duration(seconds: 1)); - status = await getPreprocessStatus(client, preprocessStatus: status); - } - - if (status.state != RecordPreprocessState.success) { - throw "Record Preprocessing failed: ${status.failReason}"; - } - return status; + static Future announceAssetUpload(ApiClient client, {required AssetManifest manifest}) async { + final response = await client.post("/users/${client.userId}/assets/${manifest.hash}/upload?size=${manifest.bytes}"); + client.checkResponse(response); + final body = jsonDecode(response.body); + return AssetUploadData.fromMap(body); } - static Future beginUploadAsset(ApiClient client, {required ResoniteDBAsset asset}) async { - final response = await client.post("/users/${client.userId}/assets/${asset.hash}/chunks"); + static Future finalizeUpload(ApiClient client, {required AssetUploadData uploadData}) async { + final response = await client.patch("/users/${client.userId}/assets/${uploadData.hash}/upload/${uploadData.id}", body: jsonEncode(uploadData.toMap())); client.checkResponse(response); - final body = jsonDecode(response.body); - final res = AssetUploadData.fromMap(body); - if (res.uploadState == UploadState.failed) throw body; - return res; } - static Future upsertRecord(ApiClient client, {required Record record}) async { - final body = jsonEncode(record.toMap()); - final response = await client.put("/users/${client.userId}/records/${record.id}", body: body); + static Future getUploadInfo(ApiClient client, {required AssetUploadData uploadData}) async { + final response = await client.patch("/users/${client.userId}/assets/${uploadData.hash}/upload/${uploadData.id}", body: jsonEncode(uploadData.toMap())); client.checkResponse(response); + final body = jsonDecode(response.body); + return AssetUploadData.fromMap(body); } - static Future uploadAsset(ApiClient client, - {required AssetUploadData uploadData, - required String filename, - required ResoniteDBAsset 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( - "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(); - client.checkResponse(http.Response.bytes(bodyBytes, response.statusCode)); - progressCallback?.call(1); - } + static Future _directAssetUpload({required AssetUploadData uploadData, required Uint8List assetData}) async { + final response = await http.put(Uri.parse(uploadData.uploadEndpoint), headers: {"Upload-Key": uploadData.uploadKey, "Upload-Timestamp": uploadData.createdOn}, body: assetData); + ApiClient.checkResponseCode(response); } - static Future finishUpload(ApiClient client, {required ResoniteDBAsset asset}) async { - final response = await client.patch("/users/${client.userId}/assets/${asset.hash}/chunks"); - client.checkResponse(response); + static Future _chunkedAssetUpload({required AssetUploadData uploadData, required int chunkIndex, required Uint8List chunkData}) async { + final response = await http.put(Uri.parse(uploadData.uploadEndpoint), headers: {"Upload-Key": uploadData.uploadKey, "Part-Number": chunkIndex.toString()}, body: chunkData); + ApiClient.checkResponseCode(response); + final body = jsonDecode(response.body); + return CloudflareChunkResult.fromMap(body); } - static Future uploadAssets(ApiClient client, {required List 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}"; + static Future> _uploadAsset( + ApiClient client, { + required AssetManifest manifest, + required Uint8List data, + void Function(double progress)? progressCallback, + }) async { + final chunkedUploads = []; + final uploadData = await announceAssetUpload(client, manifest: manifest); + if (uploadData.isDirectUpload) { + await _directAssetUpload(uploadData: uploadData, assetData: data); + } else { + final chunks = data.slices(uploadData.chunkSize); + for (final (cIdx, chunk) in chunks.indexed) { + final chunkResult = await _chunkedAssetUpload(uploadData: uploadData, chunkIndex: cIdx + 1, chunkData: Uint8List.fromList(chunk)); + final chunkInfo = AssetChunk(index: cIdx, key: chunkResult.eTag); + uploadData.chunks.add(chunkInfo); + progressCallback?.call(cIdx / chunks.length); } - 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); + chunkedUploads.add(uploadData); } + await finalizeUpload(client, uploadData: uploadData); progressCallback?.call(1); + return chunkedUploads; } - static Future 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); - final filename = basenameWithoutExtension(image.path); - - final objectJson = jsonEncode(JsonTemplate.image(imageUri: imageDigest.dbUri, filename: filename, width: imageData.width, height: imageData.height).data); - final objectBytes = Uint8List.fromList(utf8.encode(objectJson)); + static Future createRecord(ApiClient client, {required Record record, bool ensureFolder = false}) async { + final response = await client.put("/users/${client.userId}/records/${record.id}?ensureFolder=$ensureFolder", body: jsonEncode(record.toMap())); + client.checkResponse(response); + final body = jsonDecode(response.body); + return Record.fromMap(body); + } - final objectDigest = await AssetDigest.fromData(objectBytes, "${basenameWithoutExtension(image.path)}.json"); + static Future uploadImage(ApiClient client, {required File image, required String machineId, String? messageId, void Function(double progress)? progressCallback}) async { + progressCallback?.call(0); + final imageData = await image.readAsBytes(); + final cmd = img.Command() + ..decodeImage(imageData) + ..copyResize(width: 512) + ..encodeJpg(quality: 90); + final res = await cmd.executeThread(); + final thumbnail = res.outputBytes!; + final imageManifest = AssetManifest.fromData(imageData); + final thumbnailManifest = AssetManifest.fromData(thumbnail); + final assetUri = "resdb:///${imageManifest.hash}${extension(image.path)}"; + final dataTree = JsonTemplate.image(imageResDb: assetUri, machineId: machineId); + // Prefix FrDT header + final bson = Uint8List.fromList([70, 114, 68, 84, 0, 0, 0, 0] + BsonCodec.serialize(dataTree).byteList); + final bsonManifest = AssetManifest.fromData(bson); + final assetManifest = {imageManifest: imageData, thumbnailManifest: thumbnail, bsonManifest: bson}; + + final record = Record.local( + name: "Photo from ReCon", + recordType: RecordType.object, + ownerId: client.userId, + assetManifest: assetManifest.keys.toList(), + assetUri: assetUri, + tags: ["holder", "photo", "camera_photo", "texture_asset:$assetUri", "message_item", "message_id:$messageId"], + lastModifyingMachineId: machineId, + ).copyWith(thumbnailUri: () => "resdb:///${thumbnailManifest.hash}${extension(image.path)}"); + + var preproc = await preprocessRecord(client, record: record); + while (preproc.state != RecordPreprocessState.success) { + preproc = await getPreprocessStatus(client, preprocessStatus: preproc); + if (preproc.state == RecordPreprocessState.failed) { + throw "Failed to upload asset: ${preproc.failReason}"; + } + progressCallback?.call(preproc.progress * 0.2); + await Future.delayed(const Duration(seconds: 1)); + } + final chunkedUploads = []; + for (final diff in preproc.resultDiffs.where((element) => element.state == Diff.added && !(element.isUploaded ?? false))) { + final dataKey = assetManifest.keys.firstWhere((element) => element.hash == diff.hash); + final chunked = await _uploadAsset(client, manifest: diff, data: assetManifest[dataKey]!, progressCallback: (progress) => progressCallback?.call(0.2 + progress * 0.9)); + chunkedUploads.addAll(chunked); + } - final digests = [imageDigest, objectDigest]; + for (var uploadData in chunkedUploads) { + while (uploadData.uploadState != UploadState.uploaded) { + uploadData = await getUploadInfo(client, uploadData: uploadData); + if (uploadData.uploadState == UploadState.failed) { + throw "Upload failed: Unknown cloud error when combining asset chunks"; + } + await Future.delayed(const Duration(milliseconds: 1500)); + } + } - final record = Record.fromRequiredData( - recordType: RecordType.texture, - userId: client.userId, - machineId: machineId, - assetUri: objectDigest.dbUri, - filename: filename, - thumbnailUri: imageDigest.dbUri, - digests: digests, - extraTags: ["image"], - ); - progressCallback?.call(.1); - final status = await tryPreprocessRecord(client, record: record); - final toUpload = status.resultDiffs.whereNot((element) => element.isUploaded); - progressCallback?.call(.2); + await createRecord(client, record: record); - await uploadAssets(client, - assets: digests.where((digest) => toUpload.any((diff) => digest.asset.hash == diff.hash)).toList(), - progressCallback: (progress) => progressCallback?.call(.2 + progress * .6)); - await upsertRecord(client, record: record); progressCallback?.call(1); return record; } - static Future uploadVoiceClip(ApiClient client, {required File voiceClip, required String machineId, void Function(double progress)? progressCallback}) async { + static Future uploadVoiceClip( + ApiClient client, { + required File voiceClip, + required String machineId, + String? messageId, + void Function(double progress)? progressCallback, + }) async { progressCallback?.call(0); - final voiceDigest = await AssetDigest.fromData(await voiceClip.readAsBytes(), basename(voiceClip.path)); - final filename = basenameWithoutExtension(voiceClip.path); - final digests = [voiceDigest]; + final bytes = voiceClip.readAsBytesSync(); + final voiceManifest = AssetManifest.fromData(bytes); + final assetManifests = {voiceManifest: bytes}; - final record = Record.fromRequiredData( + final record = Record.local( + name: "Voice Message", recordType: RecordType.audio, - userId: client.userId, - machineId: machineId, - assetUri: voiceDigest.dbUri, - filename: filename, - thumbnailUri: "", - digests: digests, - extraTags: ["voice", "message"], + ownerId: client.userId, + assetManifest: assetManifests.keys.toList(), + assetUri: "resdb:///${voiceManifest.hash}${extension(voiceClip.path)}", + tags: ["message_item", if (messageId != null) "message_id:$messageId"], + lastModifyingMachineId: machineId, ); - 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(), - progressCallback: (progress) => progressCallback?.call(.2 + progress * .6)); - await upsertRecord(client, record: record); + var preproc = await preprocessRecord(client, record: record); + while (preproc.state != RecordPreprocessState.success) { + preproc = await getPreprocessStatus(client, preprocessStatus: preproc); + if (preproc.state == RecordPreprocessState.failed) { + throw "Failed to upload asset: ${preproc.failReason}"; + } + progressCallback?.call(preproc.progress * 0.2); + await Future.delayed(const Duration(seconds: 1)); + } + final chunkedUploads = []; + for (final diff in preproc.resultDiffs.where((element) => element.state == Diff.added && !(element.isUploaded ?? false))) { + final dataKey = assetManifests.keys.firstWhere((element) => element.hash == diff.hash); + final chunked = await _uploadAsset(client, manifest: diff, data: assetManifests[dataKey]!, progressCallback: (progress) => progressCallback?.call(0.2 + progress * 0.9)); + chunkedUploads.addAll(chunked); + } + + for (var uploadData in chunkedUploads) { + while (uploadData.uploadState != UploadState.uploaded) { + uploadData = await getUploadInfo(client, uploadData: uploadData); + if (uploadData.uploadState == UploadState.failed) { + throw "Upload failed: Unknown cloud error when combining asset chunks"; + } + await Future.delayed(const Duration(milliseconds: 1500)); + } + } + + await createRecord(client, record: record); + progressCallback?.call(1); return record; } static Future uploadRawFile(ApiClient client, {required File file, required String machineId, void Function(double progress)? progressCallback}) async { progressCallback?.call(0); - final fileDigest = await AssetDigest.fromData(await file.readAsBytes(), basename(file.path)); - - final objectJson = jsonEncode(JsonTemplate.rawFile(assetUri: fileDigest.dbUri, filename: fileDigest.name).data); - final objectBytes = Uint8List.fromList(utf8.encode(objectJson)); - - final objectDigest = await AssetDigest.fromData(objectBytes, "${basenameWithoutExtension(file.path)}.json"); - - final digests = [fileDigest, objectDigest]; - - final record = Record.fromRequiredData( - recordType: RecordType.texture, - userId: client.userId, - machineId: machineId, - assetUri: objectDigest.dbUri, - filename: fileDigest.name, - thumbnailUri: JsonTemplate.thumbUrl, - digests: digests, - extraTags: ["document"], - ); - 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(), - progressCallback: (progress) => progressCallback?.call(.2 + progress * .6)); - await upsertRecord(client, record: record); progressCallback?.call(1); - return record; + return Record.inventoryRoot(); } } diff --git a/lib/clients/api_client.dart b/lib/clients/api_client.dart index bea8744..783a36e 100644 --- a/lib/clients/api_client.dart +++ b/lib/clients/api_client.dart @@ -1,10 +1,13 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart' as http; +import 'package:http/io_client.dart'; import 'package:logging/logging.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:recon/auxiliary.dart'; import 'package:recon/models/authentication_data.dart'; import 'package:uuid/uuid.dart'; @@ -19,13 +22,14 @@ class ApiClient { static const String passwordKey = "password"; static const String uidKey = "uid"; - ApiClient({required AuthenticationData authenticationData}) : _authenticationData = authenticationData; + ApiClient({required this._authenticationData, required this._pkgInfo}); final AuthenticationData _authenticationData; final Logger _logger = Logger("API"); final _logoutNotifier = EventNotifier(); - final http.Client _client = http.Client(); + late final _client = IOClient(HttpClient()..userAgent = "${_pkgInfo.appName}/${_pkgInfo.version}"); + final PackageInfo _pkgInfo; AuthenticationData get authenticationData => _authenticationData; @@ -35,32 +39,15 @@ class ApiClient { void addLogoutListener(VoidCallback listener) => _logoutNotifier.addListener(listener); - static Future tryLogin({ - required String username, - required String password, - bool rememberMe = true, - bool rememberPass = true, - String? oneTimePad, - }) async { + static Future tryLogin({required String username, required String password, bool rememberMe = true, bool rememberPass = true, String? oneTimePad}) async { final body = { (username.contains("@") ? "email" : "username"): username.trim(), - "authentication": { - r"$type": "password", - "password": password, - }, + "authentication": {r"$type": "password", "password": password}, "rememberMe": rememberMe, "secretMachineId": const Uuid().v4(), }; final uid = const Uuid().v4().replaceAll("-", ""); - final response = await http.post( - buildFullUri("/userSessions"), - headers: { - "Content-Type": "application/json", - "UID": uid, - if (oneTimePad != null) totpKey: oneTimePad, - }, - body: jsonEncode(body), - ); + final response = await http.post(buildFullUri("/userSessions"), headers: {"Content-Type": "application/json", "UID": uid, totpKey: ?oneTimePad}, body: jsonEncode(body)); if (response.statusCode == 403 && response.body == totpKey) { throw totpKey; } @@ -72,9 +59,7 @@ class ApiClient { (data["entity"] as Map)["uid"] = uid; final authData = AuthenticationData.fromMap(data); if (authData.isAuthenticated) { - const storage = FlutterSecureStorage( - aOptions: AndroidOptions(encryptedSharedPreferences: true), - ); + const storage = FlutterSecureStorage(aOptions: AndroidOptions.defaultOptions); await storage.write(key: userIdKey, value: authData.userId); await storage.write(key: machineIdKey, value: authData.secretMachineIdHash); await storage.write(key: tokenKey, value: authData.token); @@ -85,9 +70,7 @@ class ApiClient { } static Future tryCachedLogin() async { - const storage = FlutterSecureStorage( - aOptions: AndroidOptions(encryptedSharedPreferences: true), - ); + const storage = FlutterSecureStorage(aOptions: AndroidOptions.defaultOptions); var userId = await storage.read(key: userIdKey); final machineId = await storage.read(key: machineIdKey); final token = await storage.read(key: tokenKey); @@ -99,21 +82,9 @@ class ApiClient { } if (token != null) { - final response = await http.patch( - buildFullUri("/userSessions"), - headers: { - "Authorization": "res $userId:$token", - "UID": uid, - }, - ); + final response = await http.patch(buildFullUri("/userSessions"), headers: {"Authorization": "res $userId:$token", "UID": uid}); if (response.statusCode < 300) { - return AuthenticationData( - userId: userId, - token: token, - secretMachineIdHash: machineId, - isAuthenticated: true, - uid: uid, - ); + return AuthenticationData(userId: userId, token: token, secretMachineIdHash: machineId, isAuthenticated: true, uid: uid); } } @@ -131,9 +102,7 @@ class ApiClient { Future logout() async { //TODO: Fix messaging/hub clients not being disposed on logout - const storage = FlutterSecureStorage( - aOptions: AndroidOptions(encryptedSharedPreferences: true), - ); + const storage = FlutterSecureStorage(aOptions: AndroidOptions.defaultOptions); await storage.delete(key: userIdKey); await storage.delete(key: machineIdKey); await storage.delete(key: tokenKey); @@ -162,20 +131,16 @@ class ApiClient { static void checkResponseCode(http.Response response) { if (response.statusCode < 300) return; - final error = "${response.request?.method ?? "Unknown Method"}|${response.request?.url ?? "Unknown URL"}: ${switch (response.statusCode) { - 429 => "You are being rate limited.", - 403 => "You are not authorized to do that.", - 404 => "Resource not found.", - 500 => "Internal server error.", - _ => "Unknown Error." - }} (${response.statusCode}${kDebugMode && response.body.isNotEmpty ? "|${response.body}" : ""})"; - - FlutterError.reportError( - FlutterErrorDetails( - exception: error, - stack: StackTrace.current, - ), - ); + final error = + "${response.request?.method ?? "Unknown Method"}|${response.request?.url ?? "Unknown URL"}: ${switch (response.statusCode) { + 429 => "You are being rate limited.", + 403 => "You are not authorized to do that.", + 404 => "Resource not found.", + 500 => "Internal server error.", + _ => "Unknown Error.", + }} (${response.statusCode}${kDebugMode && response.body.isNotEmpty ? "|${response.body}" : ""})"; + + FlutterError.reportError(FlutterErrorDetails(exception: error, stack: StackTrace.current)); throw error; } @@ -183,10 +148,23 @@ class ApiClient { static Uri buildFullUri(String path) => Uri.parse("${Config.apiBaseUrl}$path"); + Future _requestWrapper(Future Function() requestFunc) async { + int responseCode; + var attempts = 0; + http.Response response; + do { + await Future.delayed(Duration(seconds: attempts)); + response = await requestFunc(); + attempts++; + responseCode = response.statusCode; + } while (responseCode == 429 && attempts < 5); + return response; + } + Future get(String path, {Map? headers}) async { headers ??= {}; headers.addAll(authorizationHeader); - final response = await _client.get(buildFullUri(path), headers: headers); + final response = await _requestWrapper(() async => _client.get(buildFullUri(path), headers: headers)); _logger.info("GET $path => ${response.statusCode}${response.statusCode >= 300 ? ": ${response.body}" : ""}"); return response; } @@ -195,7 +173,7 @@ class ApiClient { headers ??= {}; headers["Content-Type"] = "application/json"; headers.addAll(authorizationHeader); - final response = await _client.post(buildFullUri(path), headers: headers, body: body); + final response = await _requestWrapper(() async => _client.post(buildFullUri(path), headers: headers, body: body)); _logger.info("PST $path => ${response.statusCode}${response.statusCode >= 300 ? ": ${response.body}" : ""}"); return response; } @@ -204,7 +182,7 @@ class ApiClient { headers ??= {}; headers["Content-Type"] = "application/json"; headers.addAll(authorizationHeader); - final response = await _client.put(buildFullUri(path), headers: headers, body: body); + final response = await _requestWrapper(() async => _client.put(buildFullUri(path), headers: headers, body: body)); _logger.info("PUT $path => ${response.statusCode}${response.statusCode >= 300 ? ": ${response.body}" : ""}"); return response; } @@ -212,7 +190,7 @@ class ApiClient { Future delete(String path, {Map? headers}) async { headers ??= {}; headers.addAll(authorizationHeader); - final response = await _client.delete(buildFullUri(path), headers: headers); + final response = await _requestWrapper(() async => _client.delete(buildFullUri(path), headers: headers)); _logger.info("DEL $path => ${response.statusCode}${response.statusCode >= 300 ? ": ${response.body}" : ""}"); return response; } @@ -221,7 +199,7 @@ class ApiClient { headers ??= {}; headers["Content-Type"] = "application/json"; headers.addAll(authorizationHeader); - final response = await _client.patch(buildFullUri(path), headers: headers, body: body); + final response = await _requestWrapper(() async => _client.patch(buildFullUri(path), headers: headers, body: body)); _logger.info("PAT $path => ${response.statusCode}${response.statusCode >= 300 ? ": ${response.body}" : ""}"); return response; } diff --git a/lib/main.dart b/lib/main.dart index 3f0bbbb..c1ae433 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -36,11 +36,7 @@ void main() async { log("Failed to initialize JustAudioMediaKit, audio features will be unavailable. Error: $e"); } SystemChrome.setSystemUIOverlayStyle( - const SystemUiOverlayStyle( - systemStatusBarContrastEnforced: true, - systemNavigationBarColor: Colors.transparent, - systemNavigationBarDividerColor: Colors.transparent, - ), + const SystemUiOverlayStyle(systemStatusBarContrastEnforced: true, systemNavigationBarColor: Colors.transparent, systemNavigationBarDividerColor: Colors.transparent), ); await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: [SystemUiOverlay.top]); @@ -48,15 +44,13 @@ void main() async { await Hive.initFlutter(); final dateFormat = DateFormat.Hms(); - Logger.root.onRecord.listen( - (event) => log("${dateFormat.format(event.time)}: ${event.message}", name: event.loggerName, time: event.time), - ); + Logger.root.onRecord.listen((event) => log("${dateFormat.format(event.time)}: ${event.message}", name: event.loggerName, time: event.time)); Logger.root.level = Level.WARNING; final settingsClient = SettingsClient(); await settingsClient.loadSettings(); final newSettings = settingsClient.currentSettings.copyWith(machineId: settingsClient.currentSettings.machineId.valueOrDefault); await settingsClient.changeSettings(newSettings); // Save generated machineId to disk - + final packageInfo = await PackageInfo.fromPlatform(); var cachedAuth = AuthenticationData.unauthenticated(); try { cachedAuth = await ApiClient.tryCachedLogin(); @@ -64,14 +58,15 @@ void main() async { // Ignore } - runApp(ReCon(settingsClient: settingsClient, cachedAuthentication: cachedAuth)); + runApp(ReCon(settingsClient: settingsClient, cachedAuthentication: cachedAuth, packageInfo: packageInfo)); } class ReCon extends StatefulWidget { - const ReCon({required this.settingsClient, required this.cachedAuthentication, super.key}); + const ReCon({required this.settingsClient, required this.cachedAuthentication, required this.packageInfo, super.key}); final SettingsClient settingsClient; final AuthenticationData cachedAuthentication; + final PackageInfo packageInfo; @override State createState() => _ReConState(); @@ -120,10 +115,7 @@ class _ReConState extends State { await showDialog( context: navigator.overlay!.context, builder: (context) { - return UpdateNotifier( - remoteVersion: remoteSem, - localVersion: currentSem, - ); + return UpdateNotifier(remoteVersion: remoteSem, localVersion: currentSem); }, ); } @@ -156,10 +148,7 @@ class _ReConState extends State { // Workaround for issue https://github.com/material-foundation/flutter-packages/issues/582 // Dynamic color schemes do not generate new additional surface container colours so we have to do it manually - (ColorScheme light, ColorScheme dark) _generateDynamicColourSchemes( - ColorScheme lightDynamic, - ColorScheme darkDynamic, - ) { + (ColorScheme light, ColorScheme dark) _generateDynamicColourSchemes(ColorScheme lightDynamic, ColorScheme darkDynamic) { final lightBase = ColorScheme.fromSeed(seedColor: lightDynamic.primary); final darkBase = ColorScheme.fromSeed(seedColor: darkDynamic.primary, brightness: Brightness.dark); @@ -173,41 +162,39 @@ class _ReConState extends State { } List _extractAdditionalColours(ColorScheme scheme) => [ - scheme.surface, - scheme.surfaceDim, - scheme.surfaceBright, - scheme.surfaceContainerLowest, - scheme.surfaceContainerLow, - scheme.surfaceContainer, - scheme.surfaceContainerHigh, - scheme.surfaceContainerHighest, - ]; + scheme.surface, + scheme.surfaceDim, + scheme.surfaceBright, + scheme.surfaceContainerLowest, + scheme.surfaceContainerLow, + scheme.surfaceContainer, + scheme.surfaceContainerHigh, + scheme.surfaceContainerHighest, + ]; ColorScheme _insertAdditionalColours(ColorScheme scheme, List additionalColours) => scheme.copyWith( - surface: additionalColours[0], - surfaceDim: additionalColours[1], - surfaceBright: additionalColours[2], - surfaceContainerLowest: additionalColours[3], - surfaceContainerLow: additionalColours[4], - surfaceContainer: additionalColours[5], - surfaceContainerHigh: additionalColours[6], - surfaceContainerHighest: additionalColours[7], - ); + surface: additionalColours[0], + surfaceDim: additionalColours[1], + surfaceBright: additionalColours[2], + surfaceContainerLowest: additionalColours[3], + surfaceContainerLow: additionalColours[4], + surfaceContainer: additionalColours[5], + surfaceContainerHigh: additionalColours[6], + surfaceContainerHighest: additionalColours[7], + ); @override Widget build(BuildContext context) { return Phoenix( child: Builder( builder: (context) { - final apiClient = ApiClient(authenticationData: _authData) - ..addLogoutListener( - () { - setState(() { - _authData = AuthenticationData.unauthenticated(); - }); - Phoenix.rebirth(context); - }, - ); + final apiClient = ApiClient(authenticationData: _authData, pkgInfo: widget.packageInfo) + ..addLogoutListener(() { + setState(() { + _authData = AuthenticationData.unauthenticated(); + }); + Phoenix.rebirth(context); + }); return ClientHolder( settingsClient: widget.settingsClient, apiClient: apiClient, @@ -246,21 +233,12 @@ class _ReConState extends State { ), ), ChangeNotifierProvider( - create: (context) => SessionClient( - apiClient: clientHolder.apiClient, - settingsClient: clientHolder.settingsClient, - ), - ), - ChangeNotifierProvider( - create: (context) => InventoryClient( - apiClient: clientHolder.apiClient, - ), + create: (context) => SessionClient(apiClient: clientHolder.apiClient, settingsClient: clientHolder.settingsClient), ), + ChangeNotifierProvider(create: (context) => InventoryClient(apiClient: clientHolder.apiClient)), ], child: AnnotatedRegion( - value: SystemUiOverlayStyle( - statusBarColor: Theme.of(context).colorScheme.surfaceContainerHighest, - ), + value: SystemUiOverlayStyle(statusBarColor: Theme.of(context).colorScheme.surfaceContainerHighest), child: const Home(), ), ) diff --git a/lib/models/records/asset_chunk.dart b/lib/models/records/asset_chunk.dart new file mode 100644 index 0000000..107935f --- /dev/null +++ b/lib/models/records/asset_chunk.dart @@ -0,0 +1,13 @@ +class AssetChunk { + final int index; + final String key; + + const AssetChunk({required this.index, required this.key}); + + factory AssetChunk.fromMap(Map map) => AssetChunk(index: map["index"], key: map["key"]); + + Map toMap() => { + "index": index, + "key": key, + }; +} diff --git a/lib/models/records/asset_diff.dart b/lib/models/records/asset_diff.dart index 49e89c5..5a588c7 100644 --- a/lib/models/records/asset_diff.dart +++ b/lib/models/records/asset_diff.dart @@ -1,9 +1,8 @@ +import 'package:recon/models/records/asset_manifest.dart'; -import 'package:recon/models/records/resonite_db_asset.dart'; - -class AssetDiff extends ResoniteDBAsset{ +class AssetDiff extends AssetManifest { final Diff state; - final bool isUploaded; + final bool? isUploaded; const AssetDiff({required super.hash, required super.bytes, required this.state, required this.isUploaded}); @@ -15,6 +14,12 @@ class AssetDiff extends ResoniteDBAsset{ isUploaded: map["isUploaded"], ); } + + @override + bool operator ==(Object other) => identical(this, other) || super == other; + + @override + int get hashCode => hash.hashCode; } enum Diff { @@ -27,8 +32,9 @@ enum Diff { } factory Diff.fromString(String? text) { - return Diff.values.firstWhere((element) => element.name.toLowerCase() == text?.toLowerCase(), + return Diff.values.firstWhere( + (element) => element.name.toLowerCase() == text?.toLowerCase(), orElse: () => Diff.unchanged, ); } -} \ No newline at end of file +} diff --git a/lib/models/records/asset_manifest.dart b/lib/models/records/asset_manifest.dart new file mode 100644 index 0000000..79d11d2 --- /dev/null +++ b/lib/models/records/asset_manifest.dart @@ -0,0 +1,33 @@ +import 'dart:typed_data'; + +import 'package:crypto/crypto.dart'; + +class AssetManifest { + final String hash; + final int bytes; + + const AssetManifest({required this.hash, required this.bytes}); + + factory AssetManifest.fromMap(Map map) { + return AssetManifest(hash: map["hash"] ?? "", bytes: map["bytes"] ?? -1); + } + + factory AssetManifest.fromData(Uint8List data) { + final digest = sha256.convert(data); + return AssetManifest(hash: digest.toString().replaceAll("-", "").toLowerCase(), bytes: data.length); + } + + Map toMap() { + return { + "hash": hash, + "bytes": bytes, + }; + } + + @override + bool operator ==(Object other) => + identical(this, other) || other is AssetManifest && runtimeType == other.runtimeType && hash == other.hash; + + @override + int get hashCode => hash.hashCode; +} diff --git a/lib/models/records/asset_upload_data.dart b/lib/models/records/asset_upload_data.dart index 6df0555..6fc2331 100644 --- a/lib/models/records/asset_upload_data.dart +++ b/lib/models/records/asset_upload_data.dart @@ -1,3 +1,5 @@ +import 'package:intl/intl.dart'; +import 'package:recon/models/records/asset_chunk.dart'; enum UploadState { uploadingChunks, @@ -7,40 +9,80 @@ enum UploadState { unknown; factory UploadState.fromString(String? text) { - return UploadState.values.firstWhere((element) => element.name.toLowerCase() == text?.toLowerCase(), + return UploadState.values.firstWhere( + (element) => element.name.toLowerCase() == text?.toLowerCase(), orElse: () => UploadState.unknown, ); } + + @override + String toString() => toBeginningOfSentenceCase(name); } class AssetUploadData { - final String signature; - final String variant; + final String hash; + final String? variant; + final String id; final String ownerId; final int totalBytes; final int chunkSize; final int totalChunks; final UploadState uploadState; + final String uploadKey; + final String uploadEndpoint; + final bool isDirectUpload; + final int maxUploadConcurrency; + final List chunks; + final String createdOn; // This is a string to prevent truncation of millisecond data which breaks asset upload HMAC checks const AssetUploadData({ - required this.signature, - required this.variant, - required this.ownerId, - required this.totalBytes, - required this.chunkSize, - required this.totalChunks, - required this.uploadState, + required this.hash, + required this.variant, + required this.id, + required this.ownerId, + required this.totalBytes, + required this.chunkSize, + required this.totalChunks, + required this.uploadState, + required this.uploadKey, + required this.uploadEndpoint, + required this.isDirectUpload, + required this.maxUploadConcurrency, + required this.chunks, + required this.createdOn, }); - factory AssetUploadData.fromMap(Map map) { - return AssetUploadData( - signature: map["signature"], - variant: map["variant"] ?? "", - ownerId: map["ownerId"] ?? "", - totalBytes: map["totalBytes"] ?? -1, - chunkSize: map["chunkSize"] ?? -1, - totalChunks: map["totalChunks"] ?? -1, - uploadState: UploadState.fromString(map["uploadStat"]), - ); - } -} \ No newline at end of file + factory AssetUploadData.fromMap(Map map) => AssetUploadData( + hash: map["hash"], + variant: map["variant"], + id: map["id"], + ownerId: map["ownerId"], + totalBytes: map["totalBytes"], + chunkSize: map["chunkSize"], + totalChunks: map["totalChunks"], + uploadState: UploadState.fromString(map["uploadState"]), + uploadKey: map["uploadKey"], + uploadEndpoint: map["uploadEndpoint"], + isDirectUpload: map["isDirectUpload"], + maxUploadConcurrency: map["maxUploadConcurrency"], + chunks: (map["chunks"] as List?)?.cast>().map(AssetChunk.fromMap).toList() ?? [], + createdOn: map["createdOn"], + ); + + Map toMap() => { + "hash": hash, + "variant": variant, + "id": id, + "ownerId": ownerId, + "totalBytes": totalBytes, + "chunkSize": chunkSize, + "totalChunks": totalChunks, + "uploadState": uploadState.toString(), + "uploadKey": uploadKey, + "uploadEndpoint": uploadEndpoint, + "isDirectUpload": isDirectUpload, + "maxUploadConcurrency": maxUploadConcurrency, + "chunks": chunks.map((e) => e.toMap()).toList(), + "createdOn": createdOn, + }; +} diff --git a/lib/models/records/cloudflare_chunk_result.dart b/lib/models/records/cloudflare_chunk_result.dart new file mode 100644 index 0000000..be99d5f --- /dev/null +++ b/lib/models/records/cloudflare_chunk_result.dart @@ -0,0 +1,11 @@ +class CloudflareChunkResult { + final String eTag; + final String checksumCRC32; + + const CloudflareChunkResult({required this.eTag, required this.checksumCRC32}); + + factory CloudflareChunkResult.fromMap(Map map) => CloudflareChunkResult( + eTag: map["ETag"], + checksumCRC32: map["ChecksumCRC32"], + ); +} diff --git a/lib/models/records/json_template.dart b/lib/models/records/json_template.dart index 9237aca..41d73c1 100644 --- a/lib/models/records/json_template.dart +++ b/lib/models/records/json_template.dart @@ -2,2799 +2,1000 @@ import 'package:path/path.dart'; import 'package:uuid/uuid.dart'; class JsonTemplate { - static const String thumbUrl = "resdb:///8ed80703e48c3d1556093927b67298f3d5e10315e9f782ec56fc49d6366f09b7.webp"; - final Map data; - JsonTemplate({required this.data}); - - factory JsonTemplate.image({required String imageUri, required String filename, 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(); - final ratio = height/width; - final 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": [ - ratio > 1 ? ratio : 1, - ratio > 1 ? 1 : ratio - ] - }, - "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": filename - }, - "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": [] - }, + static Map image({required String imageResDb, required String machineId}) { + final data = + { "TypeVersions": { - "FrooxEngine.Grabbable": 2, - "FrooxEngine.QuadMesh": 1, - "FrooxEngine.BoxCollider": 1 - } - }; - return JsonTemplate(data: data); - } - - factory JsonTemplate.rawFile({required String assetUri, required String filename}) { - final var20 = const Uuid().v4(); - final var19 = const Uuid().v4(); - final var18 = const Uuid().v4(); - final var17 = const Uuid().v4(); - final var16 = const Uuid().v4(); - final var15 = const Uuid().v4(); - final var14 = const Uuid().v4(); - final var13 = const Uuid().v4(); - final var12 = const Uuid().v4(); - final var11 = const Uuid().v4(); - final var10 = const Uuid().v4(); - final var9 = const Uuid().v4(); - final var8 = const Uuid().v4(); - final var7 = const Uuid().v4(); - final var6 = const Uuid().v4(); - final var5 = const Uuid().v4(); - final var4 = const Uuid().v4(); - final var3 = const Uuid().v4(); - final var2 = const Uuid().v4(); - final var1 = const Uuid().v4(); - final var0 = const Uuid().v4(); - final data = { - "Object": { - "ID": const Uuid().v4(), - "Components": { - "ID": const Uuid().v4(), - "Data": [ - { - "Type": "FrooxEngine.ObjectRoot", - "Data": { - "ID": const Uuid().v4(), - "persistent-ID": const Uuid().v4(), - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - } - } - }, - { - "Type": "FrooxEngine.StaticBinary", - "Data": { - "ID": var0, - "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": "@$assetUri" - } - } - }, - { - "Type": "FrooxEngine.BinaryExportable", - "Data": { - "ID": const Uuid().v4(), - "persistent-ID": const Uuid().v4(), - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "Binary": { - "ID": const Uuid().v4(), - "Data": var0 - } - } - }, - { - "Type": "FrooxEngine.FileMetadata", - "Data": { - "ID": var1, - "persistent-ID": const Uuid().v4(), - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "Filename": { - "ID": const Uuid().v4(), - "Data": filename - }, - "MIME": { - "ID": const Uuid().v4(), - "Data": null - }, - "IsProcessing-ID": const Uuid().v4() - } - }, - { - "Type": "FrooxEngine.FileVisual", - "Data": { - "ID": const Uuid().v4(), - "persistent-ID": const Uuid().v4(), - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "MetadataSource": { - "ID": const Uuid().v4(), - "Data": var1 - }, - "TypeLabel": { - "ID": const Uuid().v4(), - "Data": var2 - }, - "NameLabel": { - "ID": const Uuid().v4(), - "Data": var3 - }, - "FillMaterial": { - "ID": const Uuid().v4(), - "Data": var4 - }, - "OutlineMaterial": { - "ID": const Uuid().v4(), - "Data": var5 - }, - "TypeMaterial": { - "ID": const Uuid().v4(), - "Data": var6 - } - } - }, - { - "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() - } - } - ] - }, - "Name": { - "ID": const Uuid().v4(), - "Data": filename - }, - "Tag": { - "ID": const Uuid().v4(), - "Data": null - }, - "Active": { - "ID": const Uuid().v4(), - "Data": true - }, - "Persistent-ID": const Uuid().v4(), + "[FrooxEngine]FrooxEngine.InventoryItem": 1, + "[FrooxEngine]FrooxEngine.PhotoMetadata": 1, + "[FrooxEngine]FrooxEngine.Grabbable": 2, + "[FrooxEngine]FrooxEngine.QuadMesh": 1, + "[FrooxEngine]FrooxEngine.BoxCollider": 1 + }, + "Object": { "Position": { - "ID": const Uuid().v4(), - "Data": [ - 1.12835562, - 1.54872811, - -2.16048574 - ] - }, - "Rotation": { - "ID": const Uuid().v4(), - "Data": [ - 0.0814014, - 0.69532, - -0.07976244, - 0.7096068 - ] + "ID": "00000013-0000-0000-0000-000000000000", + "Data": [-0.03113400936126709, 0.07841096818447113, 0.1895841360092163] }, "Scale": { - "ID": const Uuid().v4(), - "Data": [ - 1.00000036, - 0.99999994, - 1.00000036 - ] + "ID": "00000015-0000-0000-0000-000000000000", + "Data": [1.0000001192092896, 1.000000238418579, 1.0000001192092896] }, - "OrderOffset": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "ParentReference": const Uuid().v4(), "Children": [ { - "ID": const Uuid().v4(), - "Components": { - "ID": const Uuid().v4(), - "Data": [] + "Persistent-ID": "000000f0-0000-0000-0000-000000000000", + "Position": { + "Data": [ + -0.008691459894180298, -0.016768932342529297, -0.0020900964736938477 + ], + "ID": "000000f1-0000-0000-0000-000000000000" }, - "Name": { - "ID": const Uuid().v4(), - "Data": "FileVisual" + "Scale": { + "ID": "000000f3-0000-0000-0000-000000000000", + "Data": [0.25000008940696716, 0.25, 0.2500000298023224] }, - "Tag": { - "ID": const Uuid().v4(), - "Data": "" + "ParentReference": "000000f5-0000-0000-0000-000000000000", + "Children": [], + "ID": "00000017-0000-0000-0000-000000000000", + "Name": { + "ID": "000000ed-0000-0000-0000-000000000000", + "Data": "Photo" }, + "Tag": { "ID": "000000ee-0000-0000-0000-000000000000", "Data": null }, "Active": { - "ID": const Uuid().v4(), + "ID": "000000ef-0000-0000-0000-000000000000", "Data": true }, - "Persistent-ID": const Uuid().v4(), - "Position": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0 - ] - }, "Rotation": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0, - 1.0 - ] - }, - "Scale": { - "ID": const Uuid().v4(), + "ID": "000000f2-0000-0000-0000-000000000000", "Data": [ - 1.0, - 1.0, - 1.0 + 0.055005572736263275, -0.013591023162007332, 0.0007486905087716877, + 0.9983932971954346 ] }, "OrderOffset": { - "ID": const Uuid().v4(), + "ID": "000000f4-0000-0000-0000-000000000000", "Data": 0 }, - "ParentReference": const Uuid().v4(), - "Children": [ - { - "ID": const Uuid().v4(), - "Components": { - "ID": const Uuid().v4(), - "Data": [ - { - "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": var7 - }, - "Materials": { - "ID": const Uuid().v4(), - "Data": [ - { - "ID": const Uuid().v4(), - "Data": var4 - }, - { - "ID": const Uuid().v4(), - "Data": var5 - }, - { - "ID": const Uuid().v4(), - "Data": var6 - } - ] - }, - "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 - } - } + "Components": { + "ID": "00000018-0000-0000-0000-000000000000", + "Data": [ + { + "Type": 2, + "Data": { + "ID": "00000019-0000-0000-0000-000000000000", + "Enabled": { + "ID": "0000001c-0000-0000-0000-000000000000", + "Data": true }, - { - "Type": "FrooxEngine.BoxCollider", - "Data": { - "ID": const Uuid().v4(), - "persistent-ID": const Uuid().v4(), - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "Offset": { - "ID": const Uuid().v4(), - "Data": [ - 0.180121541, - 0.0, - 0.0669048056 - ] - }, - "Type": { - "ID": const Uuid().v4(), - "Data": "Static" - }, - "Mass": { - "ID": const Uuid().v4(), - "Data": 1.0 - }, - "CharacterCollider": { - "ID": const Uuid().v4(), - "Data": false - }, - "IgnoreRaycasts": { - "ID": const Uuid().v4(), - "Data": false - }, - "Size": { - "ID": const Uuid().v4(), - "Data": [ - 2.360243, - 2.5, - 0.1516055 - ] - } - } + "PreferredFormat": { + "ID": "00000023-0000-0000-0000-000000000000", + "Data": null + }, + "IsNormalMap": { + "ID": "00000026-0000-0000-0000-000000000000", + "Data": false + }, + "WrapModeV": { + "ID": "00000028-0000-0000-0000-000000000000", + "Data": "Clamp" + }, + "MaxSize": { + "ID": "0000002c-0000-0000-0000-000000000000", + "Data": null + }, + "MipMaps": { + "ID": "0000002d-0000-0000-0000-000000000000", + "Data": true + }, + "URL": { + "ID": "0000001d-0000-0000-0000-000000000000", + "Data": "@$imageResDb" + }, + "AnisotropicLevel": { + "ID": "0000001f-0000-0000-0000-000000000000", + "Data": null + }, + "Uncompressed": { + "ID": "00000020-0000-0000-0000-000000000000", + "Data": false + }, + "PreferredProfile": { + "ID": "00000024-0000-0000-0000-000000000000", + "Data": null + }, + "WrapModeU": { + "ID": "00000027-0000-0000-0000-000000000000", + "Data": "Clamp" + }, + "MinSize": { + "ID": "0000002b-0000-0000-0000-000000000000", + "Data": null + }, + "MipMapFilter": { + "ID": "0000002f-0000-0000-0000-000000000000", + "Data": "Box" + }, + "Readable": { + "ID": "00000030-0000-0000-0000-000000000000", + "Data": false + }, + "UpdateOrder": { + "ID": "0000001b-0000-0000-0000-000000000000", + "Data": 0 + }, + "MipMapBias": { + "ID": "00000025-0000-0000-0000-000000000000", + "Data": 0 + }, + "PowerOfTwoAlignThreshold": { + "ID": "00000029-0000-0000-0000-000000000000", + "Data": 0.05000000074505806 + }, + "CrunchCompressed": { + "ID": "0000002a-0000-0000-0000-000000000000", + "Data": true + }, + "persistent-ID": "0000001a-0000-0000-0000-000000000000", + "FilterMode": { + "ID": "0000001e-0000-0000-0000-000000000000", + "Data": null + }, + "DirectLoad": { + "ID": "00000021-0000-0000-0000-000000000000", + "Data": false + }, + "ForceExactVariant": { + "ID": "00000022-0000-0000-0000-000000000000", + "Data": false + }, + "KeepOriginalMipMaps": { + "ID": "0000002e-0000-0000-0000-000000000000", + "Data": false } - ] - }, - "Name": { - "ID": const Uuid().v4(), - "Data": "File Mesh" - }, - "Tag": { - "ID": const Uuid().v4(), - "Data": "" + } }, - "Active": { - "ID": const Uuid().v4(), - "Data": true + { + "Type": 3, + "Data": { + "UpdateOrder": { + "Data": 0, + "ID": "00000033-0000-0000-0000-000000000000" + }, + "Enabled": { + "ID": "00000034-0000-0000-0000-000000000000", + "Data": true + }, + "Texture": { + "ID": "00000035-0000-0000-0000-000000000000", + "Data": "00000019-0000-0000-0000-000000000000" + }, + "ID": "00000031-0000-0000-0000-000000000000", + "persistent-ID": "00000032-0000-0000-0000-000000000000" + } }, - "Persistent-ID": const Uuid().v4(), - "Position": { - "ID": const Uuid().v4(), - "Data": [ - 5.96046448E-08, - 0.0, - 0.0 - ] + { + "Type": 4, + "Data": { + "UpdateOrder": { + "ID": "00000038-0000-0000-0000-000000000000", + "Data": 0 + }, + "Enabled": { + "ID": "00000039-0000-0000-0000-000000000000", + "Data": true + }, + "Texture": { + "ID": "0000003a-0000-0000-0000-000000000000", + "Data": "00000019-0000-0000-0000-000000000000" + }, + "Crop": { + "ID": "0000003b-0000-0000-0000-000000000000", + "Data": null + }, + "ID": "00000036-0000-0000-0000-000000000000", + "persistent-ID": "00000037-0000-0000-0000-000000000000" + } }, - "Rotation": { - "ID": const Uuid().v4(), - "Data": [ - -1.19209275E-07, - 0.0, - 0.0, - 1.0 - ] + { + "Type": 5, + "Data": { + "ID": "0000003c-0000-0000-0000-000000000000", + "persistent-ID": "0000003d-0000-0000-0000-000000000000", + "UpdateOrder": { + "ID": "0000003e-0000-0000-0000-000000000000", + "Data": 0 + }, + "Enabled": { + "Data": true, + "ID": "0000003f-0000-0000-0000-000000000000" + }, + "Normal": { + "ID": "00000040-0000-0000-0000-000000000000", + "Data": [0, 0, 1] + }, + "SnapParent": { + "Data": null, + "ID": "00000041-0000-0000-0000-000000000000" + } + } }, - "Scale": { - "ID": const Uuid().v4(), - "Data": [ - 0.04071409, - 0.0407139659, - 0.0407141037 - ] + { + "Type": 6, + "Data": { + "SpawnInstanceOnTrigger": { + "ID": "00000047-0000-0000-0000-000000000000", + "Data": false + }, + "ID": "00000042-0000-0000-0000-000000000000", + "persistent-ID": "00000043-0000-0000-0000-000000000000", + "UpdateOrder": { + "ID": "00000044-0000-0000-0000-000000000000", + "Data": 0 + }, + "Enabled": { + "ID": "00000045-0000-0000-0000-000000000000", + "Data": true + }, + "Reference": { + "ID": "00000046-0000-0000-0000-000000000000", + "Data": "00000019-0000-0000-0000-000000000000" + } + } }, - "OrderOffset": { - "ID": const Uuid().v4(), - "Data": 0 + { + "Type": 7, + "Data": { + "ID": "00000048-0000-0000-0000-000000000000", + "persistent-ID": "00000049-0000-0000-0000-000000000000", + "UpdateOrder": { + "ID": "0000004a-0000-0000-0000-000000000000", + "Data": 0 + }, + "Enabled": { + "Data": true, + "ID": "0000004b-0000-0000-0000-000000000000" + }, + "AssetReference": { + "ID": "0000004c-0000-0000-0000-000000000000", + "Data": "00000019-0000-0000-0000-000000000000" + } + } }, - "ParentReference": const Uuid().v4(), - "Children": [] - }, - { - "ID": const Uuid().v4(), - "Components": { - "ID": const Uuid().v4(), - "Data": [ - { - "Type": "FrooxEngine.TextRenderer", - "Data": { - "ID": var3, - "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 + { + "Type": 8, + "Data": { + "__legacyPresentUsers-ID": "0000006e-0000-0000-0000-000000000000", + "StereoLayout": { + "ID": "00000073-0000-0000-0000-000000000000", + "Data": "None" + }, + "Enabled": { + "ID": "00000050-0000-0000-0000-000000000000", + "Data": true + }, + "TakenGlobalPosition": { + "ID": "0000005e-0000-0000-0000-000000000000", + "Data": [ + 0.1488455832004547, -0.0002761205250862986, + -1.3219715356826782 + ] + }, + "UserInfos": { + "ID": "00000063-0000-0000-0000-000000000000", + "Data": [ + { + "IsPresent": { + "Data": true, + "ID": "0000006a-0000-0000-0000-000000000000" + }, + "HeadPosition": { + "ID": "0000006b-0000-0000-0000-000000000000", + "Data": [ + 0.13439173996448517, 1.663012146949768, + -1.2103259563446045 ] + }, + "HeadOrientation": { + "ID": "0000006c-0000-0000-0000-000000000000", + "Data": [ + 0.06871938705444336, -0.10541222244501114, + 0.0019961402285844088, 0.9920493960380554 + ] + }, + "SessionJoinTimestamp": { + "Data": "2026-07-05T10:08:56.132Z", + "ID": "0000006d-0000-0000-0000-000000000000" + }, + "ID": "00000064-0000-0000-0000-000000000000", + "User": { + "User": { + "ID": "00000066-0000-0000-0000-000000000000", + "Data": null + }, + "_machineId": { + "ID": "00000067-0000-0000-0000-000000000000", + "Data": machineId + }, + "_userId": { + "ID": "00000068-0000-0000-0000-000000000000", + "Data": null + }, + "ID": "00000065-0000-0000-0000-000000000000" + }, + "IsInVR": { + "Data": false, + "ID": "00000069-0000-0000-0000-000000000000" } - }, - "Font": { - "ID": const Uuid().v4(), - "Data": var8 - }, - "Text": { - "ID": const Uuid().v4(), - "Data": basenameWithoutExtension(filename) - }, - "ParseRichText": { - "ID": const Uuid().v4(), - "Data": true - }, - "NullText": { - "ID": const Uuid().v4(), - "Data": "" - }, - "Size": { - "ID": const Uuid().v4(), - "Data": 1.0 - }, - "HorizontalAlign": { - "ID": const Uuid().v4(), - "Data": "Center" - }, - "VerticalAlign": { - "ID": const Uuid().v4(), - "Data": "Top" - }, - "AlignmentMode": { - "ID": const Uuid().v4(), - "Data": "Geometric" - }, - "Color": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 1.0, - 1.0, - 1.0 - ] - }, - "Materials": { - "ID": const Uuid().v4(), - "Data": [ - { - "ID": const Uuid().v4(), - "Data": var9 - } - ] - }, - "LineHeight": { - "ID": const Uuid().v4(), - "Data": 0.8 - }, - "Bounded": { - "ID": const Uuid().v4(), - "Data": true - }, - "BoundsSize": { - "ID": const Uuid().v4(), - "Data": [ - 0.5, - 0.2 - ] - }, - "BoundsAlignment": { - "ID": const Uuid().v4(), - "Data": "MiddleCenter" - }, - "MaskPattern": { - "ID": const Uuid().v4(), - "Data": "" - }, - "HorizontalAutoSize": { - "ID": const Uuid().v4(), - "Data": true - }, - "VerticalAutoSize": { - "ID": const Uuid().v4(), - "Data": true - }, - "CaretPosition": { - "ID": const Uuid().v4(), - "Data": -1 - }, - "SelectionStart": { - "ID": const Uuid().v4(), - "Data": -1 - }, - "CaretColor": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 1.0, - 1.0, - 1.0 - ] - }, - "SelectionColor": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.5, - 0.2, - 0.5 - ] - }, - "_legacyFontMaterial-ID": const Uuid().v4(), - "_legacyAlign-ID": const Uuid().v4() - } - }, - { - "Type": "FrooxEngine.BoxCollider", - "Data": { - "ID": const Uuid().v4(), - "persistent-ID": const Uuid().v4(), - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "Offset": { - "ID": var10, - "Data": [ - 0.0, - 0.0590983443, - 0.0 - ] - }, - "Type": { - "ID": const Uuid().v4(), - "Data": "Static" - }, - "Mass": { - "ID": const Uuid().v4(), - "Data": 1.0 - }, - "CharacterCollider": { - "ID": const Uuid().v4(), - "Data": false - }, - "IgnoreRaycasts": { - "ID": const Uuid().v4(), - "Data": false - }, - "Size": { - "ID": var11, - "Data": [ - 0.5113616, - 0.09316488, - 0.0 - ] } + ] + }, + "CameraManufacturer": { + "ID": "0000006f-0000-0000-0000-000000000000", + "Data": "Resonite" + }, + "CameraFOV": { + "ID": "00000071-0000-0000-0000-000000000000", + "Data": 85 + }, + "_exportedUsers-ID": "00000074-0000-0000-0000-000000000000", + "ID": "0000004d-0000-0000-0000-000000000000", + "UpdateOrder": { + "Data": 0, + "ID": "0000004f-0000-0000-0000-000000000000" + }, + "LocationURL": { + "ID": "00000052-0000-0000-0000-000000000000", + "Data": null + }, + "LocationHost": { + "_userId": { + "Data": null, + "ID": "00000056-0000-0000-0000-000000000000" + }, + "ID": "00000053-0000-0000-0000-000000000000", + "User": { + "ID": "00000054-0000-0000-0000-000000000000", + "Data": null + }, + "_machineId": { + "ID": "00000055-0000-0000-0000-000000000000", + "Data": machineId, } }, - { - "Type": "FrooxEngine.BoundingBoxDriver", - "Data": { - "ID": const Uuid().v4(), - "persistent-ID": const Uuid().v4(), - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "BoundedSource": { - "ID": const Uuid().v4(), - "Data": var3 - }, - "Size": { - "ID": const Uuid().v4(), - "Data": var11 - }, - "Center": { - "ID": const Uuid().v4(), - "Data": var10 - }, - "Padding": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0 - ] - }, - "Scale": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 1.0, - 1.0 - ] - } + "LocationHiddenFromListing": { + "ID": "00000058-0000-0000-0000-000000000000", + "Data": false + }, + "TakenGlobalRotation": { + "ID": "0000005f-0000-0000-0000-000000000000", + "Data": [0, 0.000553934252820909, 0, 0.9999998211860657] + }, + "TakenGlobalScale": { + "Data": [1, 1, 1], + "ID": "00000060-0000-0000-0000-000000000000" + }, + "AppVersion": { + "ID": "00000061-0000-0000-0000-000000000000", + "Data": "2026.6.24.835" + }, + "LocationAccessLevel": { + "ID": "00000057-0000-0000-0000-000000000000", + "Data": "Private" + }, + "TimeTaken": { + "ID": "00000059-0000-0000-0000-000000000000", + "Data": "2026-07-05T10:10:01.042Z" + }, + "TakenBy": { + "ID": "0000005a-0000-0000-0000-000000000000", + "User": { + "Data": null, + "ID": "0000005b-0000-0000-0000-000000000000" + }, + "_machineId": { + "ID": "0000005c-0000-0000-0000-000000000000", + "Data": machineId + }, + "_userId": { + "ID": "0000005d-0000-0000-0000-000000000000", + "Data": null } + }, + "RendererName": { + "ID": "00000062-0000-0000-0000-000000000000", + "Data": "Renderite.Renderer.Unity 2026.6.23.743 (2019.4.19f1)" + }, + "CameraModel": { + "ID": "00000070-0000-0000-0000-000000000000", + "Data": "PhotoCaptureManager" + }, + "Is360": { + "ID": "00000072-0000-0000-0000-000000000000", + "Data": false + }, + "persistent-ID": "0000004e-0000-0000-0000-000000000000", + "LocationName": { + "ID": "00000051-0000-0000-0000-000000000000", + "Data": "Local" } - ] - }, - "Name": { - "ID": const Uuid().v4(), - "Data": "NameLabel" - }, - "Tag": { - "ID": const Uuid().v4(), - "Data": "" + } }, - "Active": { - "ID": const Uuid().v4(), - "Data": true + { + "Type": 9, + "Data": { + "ReparentOnRelease": { + "ID": "00000079-0000-0000-0000-000000000000", + "Data": true + }, + "GrabPriorityWhenGrabbed": { + "ID": "0000007d-0000-0000-0000-000000000000", + "Data": null + }, + "AllowSteal": { + "ID": "00000080-0000-0000-0000-000000000000", + "Data": false + }, + "UpdateOrder": { + "ID": "00000077-0000-0000-0000-000000000000", + "Data": 0 + }, + "CustomCanGrabCheck": { + "ID": "0000007e-0000-0000-0000-000000000000", + "Data": { "Target": null } + }, + "DropOnDisable": { + "ID": "00000081-0000-0000-0000-000000000000", + "Data": true + }, + "ActiveUserFilter": { + "ID": "00000082-0000-0000-0000-000000000000", + "Data": "Disabled" + }, + "OnlyUsers": { + "ID": "00000083-0000-0000-0000-000000000000", + "Data": [] + }, + "Enabled": { + "Data": true, + "ID": "00000078-0000-0000-0000-000000000000" + }, + "PreserveUserSpace": { + "ID": "0000007a-0000-0000-0000-000000000000", + "Data": true + }, + "Scalable": { + "ID": "00000084-0000-0000-0000-000000000000", + "Data": true + }, + "_grabber": { + "ID": "00000087-0000-0000-0000-000000000000", + "Data": null + }, + "__legacyActiveUserRootOnly-ID": "0000008a-0000-0000-0000-000000000000", + "DestroyOnRelease": { + "ID": "0000007b-0000-0000-0000-000000000000", + "Data": false + }, + "GrabPriority": { + "ID": "0000007c-0000-0000-0000-000000000000", + "Data": 0 + }, + "EditModeOnly": { + "ID": "0000007f-0000-0000-0000-000000000000", + "Data": false + }, + "Receivable": { + "ID": "00000085-0000-0000-0000-000000000000", + "Data": true + }, + "AllowOnlyPhysicalGrab": { + "ID": "00000086-0000-0000-0000-000000000000", + "Data": false + }, + "_lastParent": { + "ID": "00000088-0000-0000-0000-000000000000", + "Data": null + }, + "_lastParentIsUserSpace": { + "Data": false, + "ID": "00000089-0000-0000-0000-000000000000" + }, + "ID": "00000075-0000-0000-0000-000000000000", + "persistent-ID": "00000076-0000-0000-0000-000000000000" + } }, - "Persistent-ID": const Uuid().v4(), - "Position": { - "ID": const Uuid().v4(), - "Data": [ - 0.0009058714, - -0.08701205, - 0.00394916534 - ] + { + "Type": 10, + "Data": { + "Materials": { + "ID": "00000091-0000-0000-0000-000000000000", + "Data": [ + { + "ID": "00000092-0000-0000-0000-000000000000", + "Data": "00000093-0000-0000-0000-000000000000" + } + ] + }, + "MaterialPropertyBlocks": { + "ID": "00000094-0000-0000-0000-000000000000", + "Data": [] + }, + "MotionVectorMode": { + "ID": "00000096-0000-0000-0000-000000000000", + "Data": "Object" + }, + "ShadowCastMode": { + "ID": "00000095-0000-0000-0000-000000000000", + "Data": "On" + }, + "SortingOrder": { + "ID": "00000097-0000-0000-0000-000000000000", + "Data": 0 + }, + "ID": "0000008b-0000-0000-0000-000000000000", + "persistent-ID": "0000008c-0000-0000-0000-000000000000", + "UpdateOrder": { + "Data": 0, + "ID": "0000008d-0000-0000-0000-000000000000" + }, + "Enabled": { + "ID": "0000008e-0000-0000-0000-000000000000", + "Data": true + }, + "Mesh": { + "ID": "0000008f-0000-0000-0000-000000000000", + "Data": "00000090-0000-0000-0000-000000000000" + } + } }, - "Rotation": { - "ID": const Uuid().v4(), - "Data": [ - 0.0009555904, - 0.999872863, - 0.000245468284, - 0.01591436 - ] + { + "Type": 11, + "Data": { + "persistent-ID": "00000098-0000-0000-0000-000000000000", + "Enabled": { + "ID": "0000009a-0000-0000-0000-000000000000", + "Data": true + }, + "Size": { + "ID": "000000a0-0000-0000-0000-000000000000", + "Data": [0.8715755343437195, 0.4902612566947937] + }, + "LowerLeftColor": { + "ID": "000000a7-0000-0000-0000-000000000000", + "Data": [1, 1, 1, 1, "sRGB"] + }, + "UpdateOrder": { + "ID": "00000099-0000-0000-0000-000000000000", + "Data": 0 + }, + "OverrideBoundingBox": { + "ID": "0000009c-0000-0000-0000-000000000000", + "Data": false + }, + "Profile": { + "ID": "0000009e-0000-0000-0000-000000000000", + "Data": "Linear" + }, + "UVOffset": { + "ID": "000000a1-0000-0000-0000-000000000000", + "Data": [0, 0] + }, + "UVScale": { + "Data": [1, 1], + "ID": "000000a2-0000-0000-0000-000000000000" + }, + "DualSided": { + "ID": "000000a4-0000-0000-0000-000000000000", + "Data": false + }, + "UpperLeftColor": { + "Data": [1, 1, 1, 1, "sRGB"], + "ID": "000000a6-0000-0000-0000-000000000000" + }, + "LowerRightColor": { + "ID": "000000a8-0000-0000-0000-000000000000", + "Data": [1, 1, 1, 1, "sRGB"] + }, + "ID": "00000090-0000-0000-0000-000000000000", + "Rotation": { + "ID": "0000009f-0000-0000-0000-000000000000", + "Data": [0, 0, 0, 1] + }, + "ScaleUVWithSize": { + "ID": "000000a3-0000-0000-0000-000000000000", + "Data": false + }, + "UseVertexColors": { + "Data": true, + "ID": "000000a5-0000-0000-0000-000000000000" + }, + "UpperRightColor": { + "ID": "000000a9-0000-0000-0000-000000000000", + "Data": [1, 1, 1, 1, "sRGB"] + }, + "HighPriorityIntegration": { + "ID": "0000009b-0000-0000-0000-000000000000", + "Data": false + }, + "OverridenBoundingBox": { + "Data": { "Max": [0, 0, 0], "Min": [0, 0, 0] }, + "ID": "0000009d-0000-0000-0000-000000000000" + } + } }, - "Scale": { - "ID": const Uuid().v4(), - "Data": [ - 0.3075354, - 0.307534128, - 0.307536483 - ] + { + "Data": { + "UpdateOrder": { + "ID": "000000ab-0000-0000-0000-000000000000", + "Data": 0 + }, + "TintColor": { + "ID": "000000ae-0000-0000-0000-000000000000", + "Data": [1, 1, 1, 1, "sRGB"] + }, + "MaskTexture": { + "ID": "000000b2-0000-0000-0000-000000000000", + "Data": null + }, + "StereoTextureTransform": { + "Data": false, + "ID": "000000c2-0000-0000-0000-000000000000" + }, + "_unlit-ID": "000000ce-0000-0000-0000-000000000000", + "TextureOffset": { + "ID": "000000b1-0000-0000-0000-000000000000", + "Data": [0, 0] + }, + "MaskOffset": { + "Data": [0, 0], + "ID": "000000b4-0000-0000-0000-000000000000" + }, + "BlendMode": { + "ID": "000000b6-0000-0000-0000-000000000000", + "Data": "Opaque" + }, + "OffsetMagnitude": { + "ID": "000000bd-0000-0000-0000-000000000000", + "Data": [0, 0] + }, + "OffsetTextureScale": { + "ID": "000000be-0000-0000-0000-000000000000", + "Data": [1, 1] + }, + "UsePerBillboardScale": { + "Data": false, + "ID": "000000c7-0000-0000-0000-000000000000" + }, + "_unlitBillboard-ID": "000000cf-0000-0000-0000-000000000000", + "MaskMode": { + "ID": "000000b5-0000-0000-0000-000000000000", + "Data": "MultiplyAlpha" + }, + "AlphaCutoff": { + "ID": "000000b7-0000-0000-0000-000000000000", + "Data": 0.5 + }, + "OffsetTextureOffset": { + "ID": "000000bf-0000-0000-0000-000000000000", + "Data": [0, 0] + }, + "RightEyeTextureOffset": { + "ID": "000000c4-0000-0000-0000-000000000000", + "Data": [0, 0] + }, + "OffsetUnits": { + "ID": "000000cc-0000-0000-0000-000000000000", + "Data": 0 + }, + "ID": "00000093-0000-0000-0000-000000000000", + "MaskScale": { + "ID": "000000b3-0000-0000-0000-000000000000", + "Data": [1, 1] + }, + "Sidedness": { + "ID": "000000ba-0000-0000-0000-000000000000", + "Data": "Double" + }, + "DecodeAsNormalMap": { + "ID": "000000c5-0000-0000-0000-000000000000", + "Data": false + }, + "UsePerBillboardRotation": { + "ID": "000000c8-0000-0000-0000-000000000000", + "Data": false + }, + "HighPriorityIntegration": { + "ID": "000000ad-0000-0000-0000-000000000000", + "Data": false + }, + "TextureScale": { + "ID": "000000b0-0000-0000-0000-000000000000", + "Data": [1, 1] + }, + "ZWrite": { + "ID": "000000bb-0000-0000-0000-000000000000", + "Data": "Auto" + }, + "OffsetTexture": { + "Data": null, + "ID": "000000bc-0000-0000-0000-000000000000" + }, + "PolarUVmapping": { + "Data": false, + "ID": "000000c0-0000-0000-0000-000000000000" + }, + "OffsetFactor": { + "ID": "000000cb-0000-0000-0000-000000000000", + "Data": 0 + }, + "PolarPower": { + "ID": "000000c1-0000-0000-0000-000000000000", + "Data": 1 + }, + "BillboardSize": { + "ID": "000000ca-0000-0000-0000-000000000000", + "Data": [0.004999999888241291, 0.004999999888241291] + }, + "Texture": { + "Data": "00000019-0000-0000-0000-000000000000", + "ID": "000000af-0000-0000-0000-000000000000" + }, + "UseVertexColors": { + "Data": true, + "ID": "000000b8-0000-0000-0000-000000000000" + }, + "VertexColorInterpolationSpace": { + "ID": "000000b9-0000-0000-0000-000000000000", + "Data": "Linear" + }, + "UsePerBillboardUV": { + "ID": "000000c9-0000-0000-0000-000000000000", + "Data": false + }, + "RenderQueue": { + "ID": "000000cd-0000-0000-0000-000000000000", + "Data": -1 + }, + "persistent-ID": "000000aa-0000-0000-0000-000000000000", + "Enabled": { + "Data": true, + "ID": "000000ac-0000-0000-0000-000000000000" + }, + "RightEyeTextureScale": { + "ID": "000000c3-0000-0000-0000-000000000000", + "Data": [1, 1] + }, + "UseBillboardGeometry": { + "Data": false, + "ID": "000000c6-0000-0000-0000-000000000000" + } + }, + "Type": 12 }, - "OrderOffset": { - "ID": const Uuid().v4(), - "Data": 0 + { + "Type": 13, + "Data": { + "persistent-ID": "000000d1-0000-0000-0000-000000000000", + "Enabled": { + "ID": "000000d3-0000-0000-0000-000000000000", + "Data": true + }, + "Target": { + "ID": "000000d5-0000-0000-0000-000000000000", + "Data": "000000a0-0000-0000-0000-000000000000" + }, + "Premultiply": { + "ID": "000000d7-0000-0000-0000-000000000000", + "Data": [1, 1] + }, + "Ratio": { + "ID": "000000d8-0000-0000-0000-000000000000", + "Data": [1, 1] + }, + "MaxSize": { + "ID": "000000d9-0000-0000-0000-000000000000", + "Data": [3.4028234663852886e38, 3.4028234663852886e38] + }, + "ID": "000000d0-0000-0000-0000-000000000000", + "UpdateOrder": { + "ID": "000000d2-0000-0000-0000-000000000000", + "Data": 0 + }, + "Texture": { + "ID": "000000d4-0000-0000-0000-000000000000", + "Data": "00000019-0000-0000-0000-000000000000" + }, + "DriveMode": { + "ID": "000000d6-0000-0000-0000-000000000000", + "Data": "Normalized" + } + } }, - "ParentReference": const Uuid().v4(), - "Children": [] - }, - { - "ID": const Uuid().v4(), - "Components": { - "ID": const Uuid().v4(), - "Data": [ - { - "Type": "FrooxEngine.TextRenderer", - "Data": { - "ID": var2, - "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 - ] - } - }, - "Font": { - "ID": const Uuid().v4(), - "Data": var8 - }, - "Text": { - "ID": const Uuid().v4(), - "Data": extension(filename).toUpperCase() - }, - "ParseRichText": { - "ID": const Uuid().v4(), - "Data": true - }, - "NullText": { - "ID": const Uuid().v4(), - "Data": "" - }, - "Size": { - "ID": const Uuid().v4(), - "Data": 1.0 - }, - "HorizontalAlign": { - "ID": const Uuid().v4(), - "Data": "Center" - }, - "VerticalAlign": { - "ID": const Uuid().v4(), - "Data": "Middle" - }, - "AlignmentMode": { - "ID": const Uuid().v4(), - "Data": "Geometric" - }, - "Color": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 1.0, - 1.0, - 1.0 - ] - }, - "Materials": { - "ID": const Uuid().v4(), - "Data": [ - { - "ID": const Uuid().v4(), - "Data": var9 - } - ] - }, - "LineHeight": { - "ID": const Uuid().v4(), - "Data": 0.8 - }, - "Bounded": { - "ID": const Uuid().v4(), - "Data": true - }, - "BoundsSize": { - "ID": const Uuid().v4(), - "Data": [ - 0.24, - 1.0 - ] - }, - "BoundsAlignment": { - "ID": const Uuid().v4(), - "Data": "MiddleCenter" - }, - "MaskPattern": { - "ID": const Uuid().v4(), - "Data": "" - }, - "HorizontalAutoSize": { - "ID": const Uuid().v4(), - "Data": true - }, - "VerticalAutoSize": { - "ID": const Uuid().v4(), - "Data": true - }, - "CaretPosition": { - "ID": const Uuid().v4(), - "Data": -1 - }, - "SelectionStart": { - "ID": const Uuid().v4(), - "Data": -1 - }, - "CaretColor": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 1.0, - 1.0, - 1.0 - ] - }, - "SelectionColor": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.5, - 0.2, - 0.5 - ] - }, - "_legacyFontMaterial-ID": const Uuid().v4(), - "_legacyAlign-ID": const Uuid().v4() - } + { + "Type": 14, + "Data": { + "ID": "000000da-0000-0000-0000-000000000000", + "persistent-ID": "000000db-0000-0000-0000-000000000000", + "Offset": { + "ID": "000000de-0000-0000-0000-000000000000", + "Data": [0, 0, 0] + }, + "Type": { + "Data": "NoCollision", + "ID": "000000df-0000-0000-0000-000000000000" + }, + "Mass": { + "ID": "000000e0-0000-0000-0000-000000000000", + "Data": 1 + }, + "IgnoreRaycasts": { + "ID": "000000e2-0000-0000-0000-000000000000", + "Data": false + }, + "Size": { + "ID": "000000e3-0000-0000-0000-000000000000", + "Data": [0.8715755343437195, 0.4902612566947937, 0] + }, + "UpdateOrder": { + "ID": "000000dc-0000-0000-0000-000000000000", + "Data": 1000000 }, - { - "Type": "FrooxEngine.BoxCollider", - "Data": { - "ID": const Uuid().v4(), - "persistent-ID": const Uuid().v4(), - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "Offset": { - "ID": var12, - "Data": [ - -3.7252903E-09, - 0.0, - 0.0 - ] - }, - "Type": { - "ID": const Uuid().v4(), - "Data": "Static" - }, - "Mass": { - "ID": const Uuid().v4(), - "Data": 1.0 - }, - "CharacterCollider": { - "ID": const Uuid().v4(), - "Data": false - }, - "IgnoreRaycasts": { - "ID": const Uuid().v4(), - "Data": false - }, - "Size": { - "ID": var13, - "Data": [ - 0.1862, - 0.08590001, - 0.0 - ] - } - } + "Enabled": { + "ID": "000000dd-0000-0000-0000-000000000000", + "Data": true }, - { - "Type": "FrooxEngine.BoundingBoxDriver", - "Data": { - "ID": const Uuid().v4(), - "persistent-ID": const Uuid().v4(), - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "BoundedSource": { - "ID": const Uuid().v4(), - "Data": var2 - }, - "Size": { - "ID": const Uuid().v4(), - "Data": var13 - }, - "Center": { - "ID": const Uuid().v4(), - "Data": var12 - }, - "Padding": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0 - ] - }, - "Scale": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 1.0, - 1.0 - ] - } - } + "CharacterCollider": { + "ID": "000000e1-0000-0000-0000-000000000000", + "Data": false } - ] - }, - "Name": { - "ID": const Uuid().v4(), - "Data": "TypeLabel" - }, - "Tag": { - "ID": const Uuid().v4(), - "Data": "" - }, - "Active": { - "ID": const Uuid().v4(), - "Data": true - }, - "Persistent-ID": const Uuid().v4(), - "Position": { - "ID": const Uuid().v4(), - "Data": [ - 0.02074349, - 0.02509594, - 0.00547504425 - ] - }, - "Rotation": { - "ID": const Uuid().v4(), - "Data": [ - 3.05048379E-05, - 0.9999975, - -0.000117197917, - -0.0022352722 - ] - }, - "Scale": { - "ID": const Uuid().v4(), - "Data": [ - 0.268987477, - 0.2689861, - 0.268988162 - ] - }, - "OrderOffset": { - "ID": const Uuid().v4(), - "Data": 0 + } }, - "ParentReference": const Uuid().v4(), - "Children": [] - }, - { - "ID": const Uuid().v4(), - "Components": { - "ID": const Uuid().v4(), - "Data": [ - { - "Type": "FrooxEngine.Panner2D", - "Data": { - "ID": const Uuid().v4(), - "persistent-ID": const Uuid().v4(), - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "_target": { - "ID": const Uuid().v4(), - "Data": var14 - }, - "_offset": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0 - ] - }, - "_preOffset": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0 - ] - }, - "_speed": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 0.0 - ] - }, - "_repeat": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 1.0 - ] - }, - "PingPong": { - "ID": const Uuid().v4(), - "Data": false - } - } + { + "Type": 15, + "Data": { + "X": { + "ID": "000000ea-0000-0000-0000-000000000000", + "Data": 0 }, - { - "Type": "FrooxEngine.PBS_DualSidedMetallic", - "Data": { - "ID": var5, - "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 - }, - "TextureScale": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 1.0 - ] - }, - "TextureOffset": { - "ID": var14, - "Data": [ - 0.399169922, - 0.0 - ] - }, - "AlbedoColor": { - "ID": const Uuid().v4(), - "Data": [ - 0.25, - 0.25, - 0.25, - 1.0 - ] - }, - "AlbedoTexture": { - "ID": const Uuid().v4(), - "Data": null - }, - "EmissiveColor": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0, - 1.0 - ] - }, - "EmissiveMap": { - "ID": const Uuid().v4(), - "Data": var15 - }, - "NormalMap": { - "ID": const Uuid().v4(), - "Data": null - }, - "NormalScale": { - "ID": const Uuid().v4(), - "Data": 1.0 - }, - "OcclusionMap": { - "ID": const Uuid().v4(), - "Data": null - }, - "Culling": { - "ID": const Uuid().v4(), - "Data": "Off" - }, - "AlphaHandling": { - "ID": const Uuid().v4(), - "Data": "Opaque" - }, - "AlphaClip": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "OffsetFactor": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "OffsetUnits": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "RenderQueue": { - "ID": const Uuid().v4(), - "Data": -1 - }, - "Metallic": { - "ID": const Uuid().v4(), - "Data": 1.0 - }, - "Smoothness": { - "ID": const Uuid().v4(), - "Data": 0.9 - }, - "MetallicMap": { - "ID": const Uuid().v4(), - "Data": null - }, - "_regular-ID": const Uuid().v4(), - "_transparent-ID": const Uuid().v4() - } + "Y": { + "Data": 1, + "ID": "000000eb-0000-0000-0000-000000000000" + }, + "UpdateOrder": { + "ID": "000000e6-0000-0000-0000-000000000000", + "Data": 0 + }, + "Target": { + "ID": "000000e9-0000-0000-0000-000000000000", + "Data": "000000e3-0000-0000-0000-000000000000" + }, + "Z": { + "ID": "000000ec-0000-0000-0000-000000000000", + "Data": -1 + }, + "ID": "000000e4-0000-0000-0000-000000000000", + "persistent-ID": "000000e5-0000-0000-0000-000000000000", + "Enabled": { + "ID": "000000e7-0000-0000-0000-000000000000", + "Data": true + }, + "Source": { + "ID": "000000e8-0000-0000-0000-000000000000", + "Data": "000000a0-0000-0000-0000-000000000000" } - ] - }, - "Name": { - "ID": const Uuid().v4(), - "Data": "OutlineMaterial" + } + } + ] + } + } + ], + "Name": { "ID": "0000000f-0000-0000-0000-000000000000", "Data": "Holder" }, + "Persistent-ID": "00000012-0000-0000-0000-000000000000", + "Rotation": { + "ID": "00000014-0000-0000-0000-000000000000", + "Data": [ + 0.17878229916095734, 0.11211946606636047, 0.6981614828109741, + 0.6841321587562561 + ] + }, + "OrderOffset": { "Data": 0, "ID": "00000016-0000-0000-0000-000000000000" }, + "ParentReference": "000000f6-0000-0000-0000-000000000000", + "ID": "00000000-0000-0000-0000-000000000000", + "Components": { + "ID": "00000001-0000-0000-0000-000000000000", + "Data": [ + { + "Data": { + "MaxDepth": { + "ID": "00000007-0000-0000-0000-000000000000", + "Data": 1 }, - "Tag": { - "ID": const Uuid().v4(), - "Data": "" + "ID": "00000002-0000-0000-0000-000000000000", + "persistent-ID": "00000003-0000-0000-0000-000000000000", + "UpdateOrder": { + "Data": 0, + "ID": "00000004-0000-0000-0000-000000000000" }, - "Active": { - "ID": const Uuid().v4(), + "Enabled": { + "ID": "00000005-0000-0000-0000-000000000000", "Data": true }, - "Persistent-ID": const Uuid().v4(), - "Position": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0 - ] - }, - "Rotation": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0, - 1.0 - ] - }, - "Scale": { - "ID": const Uuid().v4(), - "Data": [ - 0.000407140964, - 0.000407139567, - 0.000407140964 - ] - }, - "OrderOffset": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "ParentReference": const Uuid().v4(), - "Children": [] - }, - { - "ID": const Uuid().v4(), - "Components": { - "ID": const Uuid().v4(), - "Data": [ - { - "Type": "FrooxEngine.PBS_DualSidedMetallic", - "Data": { - "ID": var4, - "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 - }, - "TextureScale": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 1.0 - ] - }, - "TextureOffset": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0 - ] - }, - "AlbedoColor": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 1.0, - 1.0, - 1.0 - ] - }, - "AlbedoTexture": { - "ID": const Uuid().v4(), - "Data": null - }, - "EmissiveColor": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0, - 1.0 - ] - }, - "EmissiveMap": { - "ID": const Uuid().v4(), - "Data": null - }, - "NormalMap": { - "ID": const Uuid().v4(), - "Data": null - }, - "NormalScale": { - "ID": const Uuid().v4(), - "Data": 1.0 - }, - "OcclusionMap": { - "ID": const Uuid().v4(), - "Data": null - }, - "Culling": { - "ID": const Uuid().v4(), - "Data": "Off" - }, - "AlphaHandling": { - "ID": const Uuid().v4(), - "Data": "Opaque" - }, - "AlphaClip": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "OffsetFactor": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "OffsetUnits": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "RenderQueue": { - "ID": const Uuid().v4(), - "Data": -1 - }, - "Metallic": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "Smoothness": { - "ID": const Uuid().v4(), - "Data": 0.75 - }, - "MetallicMap": { - "ID": const Uuid().v4(), - "Data": null - }, - "_regular-ID": const Uuid().v4(), - "_transparent-ID": const Uuid().v4() - } - } - ] - }, - "Name": { - "ID": const Uuid().v4(), - "Data": "FillMaterial" - }, - "Tag": { - "ID": const Uuid().v4(), - "Data": "" - }, - "Active": { - "ID": const Uuid().v4(), + "DontReparent": { + "ID": "00000006-0000-0000-0000-000000000000", "Data": true - }, - "Persistent-ID": const Uuid().v4(), - "Position": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0 - ] - }, - "Rotation": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0, - 1.0 - ] - }, - "Scale": { - "ID": const Uuid().v4(), - "Data": [ - 0.000407140964, - 0.000407139567, - 0.000407140964 - ] - }, - "OrderOffset": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "ParentReference": const Uuid().v4(), - "Children": [] + } }, - { - "ID": const Uuid().v4(), - "Components": { - "ID": const Uuid().v4(), - "Data": [ - { - "Type": "FrooxEngine.PBS_DualSidedMetallic", - "Data": { - "ID": var6, - "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 - }, - "TextureScale": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 1.0 - ] - }, - "TextureOffset": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0 - ] - }, - "AlbedoColor": { - "ID": const Uuid().v4(), - "Data": [ - 0.25, - 0.25, - 0.25, - 1.0 - ] - }, - "AlbedoTexture": { - "ID": const Uuid().v4(), - "Data": null - }, - "EmissiveColor": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0, - 1.0 - ] - }, - "EmissiveMap": { - "ID": const Uuid().v4(), - "Data": null - }, - "NormalMap": { - "ID": const Uuid().v4(), - "Data": null - }, - "NormalScale": { - "ID": const Uuid().v4(), - "Data": 1.0 - }, - "OcclusionMap": { - "ID": const Uuid().v4(), - "Data": null - }, - "Culling": { - "ID": const Uuid().v4(), - "Data": "Off" - }, - "AlphaHandling": { - "ID": const Uuid().v4(), - "Data": "Opaque" - }, - "AlphaClip": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "OffsetFactor": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "OffsetUnits": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "RenderQueue": { - "ID": const Uuid().v4(), - "Data": -1 - }, - "Metallic": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "Smoothness": { - "ID": const Uuid().v4(), - "Data": 0.8 - }, - "MetallicMap": { - "ID": const Uuid().v4(), - "Data": null - }, - "_regular-ID": const Uuid().v4(), - "_transparent-ID": const Uuid().v4() - } - } - ] - }, - "Name": { - "ID": const Uuid().v4(), - "Data": "TypeMaterial" - }, - "Tag": { - "ID": const Uuid().v4(), - "Data": "" + "Type": 0 + }, + { + "Type": 1, + "Data": { + "UpdateOrder": { + "ID": "0000000a-0000-0000-0000-000000000000", + "Data": 0 }, - "Active": { - "ID": const Uuid().v4(), + "Enabled": { + "ID": "0000000b-0000-0000-0000-000000000000", "Data": true }, - "Persistent-ID": const Uuid().v4(), - "Position": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0 - ] - }, - "Rotation": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0, - 1.0 - ] + "RelativeToUserRoot": { + "ID": "0000000c-0000-0000-0000-000000000000", + "Data": true }, - "Scale": { - "ID": const Uuid().v4(), + "SavedRotation": { + "ID": "0000000d-0000-0000-0000-000000000000", "Data": [ - 0.000407140964, - 0.000407139567, - 0.000407140964 + 7.440260851865332e-9, -0.08889392763376236, + 1.8630576192890658e-8, 0.9960411787033081 ] }, - "OrderOffset": { - "ID": const Uuid().v4(), - "Data": 0 + "SavedScale": { + "ID": "0000000e-0000-0000-0000-000000000000", + "Data": [1, 1, 1] }, - "ParentReference": const Uuid().v4(), - "Children": [] + "ID": "00000008-0000-0000-0000-000000000000", + "persistent-ID": "00000009-0000-0000-0000-000000000000" } - ] - } - ] - }, - "Assets": [ - { - "Type": "FrooxEngine.StaticMesh", - "Data": { - "ID": var7, - "persistent": { - "ID": const Uuid().v4(), - "Data": true - }, - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "URL": { - "ID": const Uuid().v4(), - "Data": "@resdb:///3738bf6fc560f7d08d872ce12b06f4d9337ac5da415b6de6008a49ca128658ec" - }, - "Readable": { - "ID": const Uuid().v4(), - "Data": false - } - } - }, - { - "Type": "FrooxEngine.FontChain", - "Data": { - "ID": var8, - "persistent": { - "ID": const Uuid().v4(), - "Data": true - }, - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "HighPriorityIntegration": { - "ID": const Uuid().v4(), - "Data": false - }, - "MainFont": { - "ID": const Uuid().v4(), - "Data": var16 - }, - "FallbackFonts": { - "ID": const Uuid().v4(), - "Data": [ - { - "ID": const Uuid().v4(), - "Data": var17 - }, - { - "ID": const Uuid().v4(), - "Data": var18 - }, - { - "ID": const Uuid().v4(), - "Data": var19 - }, - { - "ID": const Uuid().v4(), - "Data": var20 - } - ] - } - } - }, - { - "Type": "FrooxEngine.StaticFont", - "Data": { - "ID": var16, - "persistent": { - "ID": const Uuid().v4(), - "Data": true - }, - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "URL": { - "ID": const Uuid().v4(), - "Data": "@resdb:///c801b8d2522fb554678f17f4597158b1af3f9be3abd6ce35d5a3112a81e2bf39" - }, - "Padding": { - "ID": const Uuid().v4(), - "Data": 1 - }, - "PixelRange": { - "ID": const Uuid().v4(), - "Data": 4 - }, - "GlyphEmSize": { - "ID": const Uuid().v4(), - "Data": 32 - } - } - }, - { - "Type": "FrooxEngine.StaticFont", - "Data": { - "ID": var17, - "persistent": { - "ID": const Uuid().v4(), - "Data": true - }, - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "URL": { - "ID": const Uuid().v4(), - "Data": "@resdb:///4cac521169034ddd416c6deffe2eb16234863761837df677a910697ec5babd25" - }, - "Padding": { - "ID": const Uuid().v4(), - "Data": 1 - }, - "PixelRange": { - "ID": const Uuid().v4(), - "Data": 4 - }, - "GlyphEmSize": { - "ID": const Uuid().v4(), - "Data": 32 - } - } - }, - { - "Type": "FrooxEngine.StaticFont", - "Data": { - "ID": var18, - "persistent": { - "ID": const Uuid().v4(), - "Data": true - }, - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "URL": { - "ID": const Uuid().v4(), - "Data": "@resdb:///23e7ad7cb0a5a4cf75e07c9e0848b1eb06bba15e8fa9b8cb0579fc823c532927" - }, - "Padding": { - "ID": const Uuid().v4(), - "Data": 1 - }, - "PixelRange": { - "ID": const Uuid().v4(), - "Data": 4 - }, - "GlyphEmSize": { - "ID": const Uuid().v4(), - "Data": 32 - } - } - }, - { - "Type": "FrooxEngine.StaticFont", - "Data": { - "ID": var19, - "persistent": { - "ID": const Uuid().v4(), - "Data": true - }, - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "URL": { - "ID": const Uuid().v4(), - "Data": "@resdb:///415dc6290378574135b64c808dc640c1df7531973290c4970c51fdeb849cb0c5" - }, - "Padding": { - "ID": const Uuid().v4(), - "Data": 1 - }, - "PixelRange": { - "ID": const Uuid().v4(), - "Data": 4 - }, - "GlyphEmSize": { - "ID": const Uuid().v4(), - "Data": 32 - } - } - }, - { - "Type": "FrooxEngine.StaticFont", - "Data": { - "ID": var20, - "persistent": { - "ID": const Uuid().v4(), - "Data": true - }, - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "URL": { - "ID": const Uuid().v4(), - "Data": "@resdb:///bcda0bcc22bab28ea4fedae800bfbf9ec76d71cc3b9f851779a35b7e438a839d" - }, - "Padding": { - "ID": const Uuid().v4(), - "Data": 1 - }, - "PixelRange": { - "ID": const Uuid().v4(), - "Data": 4 - }, - "GlyphEmSize": { - "ID": const Uuid().v4(), - "Data": 32 } - } - }, - { - "Type": "FrooxEngine.TextUnlitMaterial", - "Data": { - "ID": var9, - "persistent": { - "ID": const Uuid().v4(), - "Data": true - }, - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "HighPriorityIntegration": { - "ID": const Uuid().v4(), - "Data": false - }, - "_shader-ID": const Uuid().v4(), - "FontAtlas": { - "ID": const Uuid().v4(), - "Data": null - }, - "TintColor": { - "ID": const Uuid().v4(), - "Data": [ - 1.0, - 1.0, - 1.0, - 1.0 - ] - }, - "OutlineColor": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0, - 1.0 - ] - }, - "BackgroundColor": { - "ID": const Uuid().v4(), - "Data": [ - 0.0, - 0.0, - 0.0, - 1.0 - ] - }, - "AutoBackgroundColor": { - "ID": const Uuid().v4(), - "Data": true - }, - "GlyphRenderMethod": { - "ID": const Uuid().v4(), - "Data": "MSDF" - }, - "PixelRange": { - "ID": const Uuid().v4(), - "Data": 4.0 - }, - "FaceDilate": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "OutlineThickness": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "FaceSoftness": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "BlendMode": { - "ID": const Uuid().v4(), - "Data": "Alpha" - }, - "Sidedness": { - "ID": const Uuid().v4(), - "Data": "Double" - }, - "ZWrite": { - "ID": const Uuid().v4(), - "Data": "Auto" - }, - "ZTest": { - "ID": const Uuid().v4(), - "Data": "LessOrEqual" - }, - "OffsetFactor": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "OffsetUnits": { - "ID": const Uuid().v4(), - "Data": 0.0 - }, - "RenderQueue": { - "ID": const Uuid().v4(), - "Data": -1 - } - } + ] }, - { - "Type": "FrooxEngine.StaticTexture2D", - "Data": { - "ID": var15, - "persistent": { - "ID": const Uuid().v4(), - "Data": true - }, - "UpdateOrder": { - "ID": const Uuid().v4(), - "Data": 0 - }, - "Enabled": { - "ID": const Uuid().v4(), - "Data": true - }, - "URL": { - "ID": const Uuid().v4(), - "Data": "@resdb:///274f0d4ea4bce93abc224c9ae9f9a97a9a396b382c5338f71c738d1591dd5c35.webp" - }, - "FilterMode": { - "ID": const Uuid().v4(), - "Data": "Anisotropic" - }, - "AnisotropicLevel": { - "ID": const Uuid().v4(), - "Data": 8 - }, - "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 - } - } - } - ], - "TypeVersions": { - "FrooxEngine.Grabbable": 2, - "FrooxEngine.BoxCollider": 1, - "FrooxEngine.TextRenderer": 5 - } + "Tag": { "ID": "00000010-0000-0000-0000-000000000000", "Data": null }, + "Active": { "Data": true, "ID": "00000011-0000-0000-0000-000000000000" } + }, + "VersionNumber": "2026.6.24.835", + "FeatureFlags": { + "NetCore": 0, + "TEXTURE_QUALITY": 0, + "TypeManagement": 0, + "PhotonDust": 0, + "RESONITE_LINK": 0, + "ColorManagement": 0, + "ResetGUID": 0, + "ProtoFlux": 0, + "ALIGNER_FILTERING": 0, + "Awwdio": 0 + }, + "Types": [ + "[FrooxEngine]FrooxEngine.GrabbableReparentBlock", + "[FrooxEngine]FrooxEngine.InventoryItem", + "[FrooxEngine]FrooxEngine.StaticTexture2D", + "[FrooxEngine]FrooxEngine.TextureExportable", + "[FrooxEngine]FrooxEngine.ItemTextureThumbnailSource", + "[FrooxEngine]FrooxEngine.SnapPlane", + "[FrooxEngine]FrooxEngine.ReferenceProxy", + "[FrooxEngine]FrooxEngine.AssetProxy<[FrooxEngine]FrooxEngine.Texture2D>", + "[FrooxEngine]FrooxEngine.PhotoMetadata", + "[FrooxEngine]FrooxEngine.Grabbable", + "[FrooxEngine]FrooxEngine.MeshRenderer", + "[FrooxEngine]FrooxEngine.QuadMesh", + "[FrooxEngine]FrooxEngine.UnlitMaterial", + "[FrooxEngine]FrooxEngine.TextureSizeDriver", + "[FrooxEngine]FrooxEngine.BoxCollider", + "[FrooxEngine]FrooxEngine.Float2ToFloat3SwizzleDriver" + ] }; - return JsonTemplate(data: data); + return data; } } \ No newline at end of file diff --git a/lib/models/records/preprocess_status.dart b/lib/models/records/preprocess_status.dart index 675b1c8..0d08013 100644 --- a/lib/models/records/preprocess_status.dart +++ b/lib/models/records/preprocess_status.dart @@ -1,30 +1,39 @@ import 'package:recon/models/records/asset_diff.dart'; -enum RecordPreprocessState -{ +enum RecordPreprocessState { preprocessing, success, failed; factory RecordPreprocessState.fromString(String? text) { - return RecordPreprocessState.values.firstWhere((element) => element.name.toLowerCase() == text?.toLowerCase(), + return RecordPreprocessState.values.firstWhere( + (element) => element.name.toLowerCase() == text?.toLowerCase(), orElse: () => RecordPreprocessState.failed, ); } } - class PreprocessStatus { final String id; final String ownerId; final String recordId; final RecordPreprocessState state; - final num progress; - final String failReason; + final double progress; + final String? failReason; final List resultDiffs; + final int attempts; + final int ttl; - const PreprocessStatus({required this.id, required this.ownerId, required this.recordId, required this.state, - required this.progress, required this.failReason, required this.resultDiffs, + const PreprocessStatus({ + required this.id, + required this.ownerId, + required this.recordId, + required this.state, + required this.progress, + required this.failReason, + required this.resultDiffs, + required this.attempts, + required this.ttl, }); factory PreprocessStatus.fromMap(Map map) { @@ -33,9 +42,11 @@ class PreprocessStatus { ownerId: map["ownerId"], recordId: map["recordId"], state: RecordPreprocessState.fromString(map["state"]), - progress: map["progress"], - failReason: map["failReason"] ?? "", + progress: (map["progress"] as num).toDouble(), + failReason: map["failReason"], resultDiffs: (map["resultDiffs"] as List? ?? []).map((e) => AssetDiff.fromMap(e)).toList(), + attempts: map["attempts"], + ttl: map["ttl"], ); } -} \ No newline at end of file +} diff --git a/lib/models/records/record.dart b/lib/models/records/record.dart index 32cc467..6c76c7a 100644 --- a/lib/models/records/record.dart +++ b/lib/models/records/record.dart @@ -1,7 +1,6 @@ import 'package:recon/auxiliary.dart'; -import 'package:recon/models/message.dart'; -import 'package:recon/models/records/asset_digest.dart'; -import 'package:recon/models/records/resonite_db_asset.dart'; +import 'package:recon/models/records/asset_manifest.dart'; +import 'package:recon/models/records/record_version.dart'; import 'package:recon/string_formatter.dart'; import 'package:uuid/uuid.dart'; @@ -14,40 +13,26 @@ enum RecordType { audio; factory RecordType.fromName(String? name) { - return RecordType.values.firstWhere((element) => element.name.toLowerCase() == name?.toLowerCase().trim(), orElse: () => RecordType.unknown); - } -} - -class RecordId { - final String? id; - final String? ownerId; - final bool isValid; - - const RecordId({this.id, this.ownerId, required this.isValid}); - - factory RecordId.fromMap(Map? map) { - return RecordId(id: map?["id"], ownerId: map?["ownerId"], isValid: map?["isValid"] ?? false); - } - - Map toMap() { - return { - "id": id, - "ownerId": ownerId, - "isValid": isValid, - }; + return RecordType.values.firstWhere( + (element) => element.name.toLowerCase() == name?.toLowerCase().trim(), + orElse: () => RecordType.unknown, + ); } } class Record { static final _rootRecord = Record( id: "0", - combinedRecordId: const RecordId(isValid: false), - isSynced: true, - fetchedOn: DateTimeX.epoch, path: "Inventory", ownerId: "", assetUri: "", name: "Inventory", + version: RecordVersion( + globalVersion: 1, + localVersion: 1, + lastModifyingUserId: null, + lastModifyingMachineId: null, + ), description: "", tags: [], recordType: RecordType.directory, @@ -56,58 +41,47 @@ class Record { isListed: false, isForPatreons: false, lastModificationTime: DateTimeX.epoch, - resoniteDBManifest: [], - lastModifyingUserId: "", - lastModifyingMachineId: "", creationTime: DateTimeX.epoch, - manifest: [], - url: "", - isValidOwnerId: true, - isValidRecordId: true, - globalVersion: 1, - localVersion: 1, + assetManifest: [], visits: 0, rating: 0, randomOrder: 0, + ownerName: null, + isDeleted: false, + isReadOnly: true, + firstPublishTime: DateTimeX.epoch, + rootRecordId: null, ); final String id; - final RecordId combinedRecordId; final String ownerId; final String assetUri; - final int globalVersion; - final int localVersion; - final String lastModifyingUserId; - final String lastModifyingMachineId; - final bool isSynced; - final DateTime fetchedOn; + final RecordVersion version; final String name; final FormatNode formattedName; - final String description; - final FormatNode formattedDescription; + final String? description; + final FormatNode? formattedDescription; final RecordType recordType; + final String? ownerName; final List tags; - final String path; - final String thumbnailUri; + final String? path; + final String? thumbnailUri; final bool isPublic; final bool isForPatreons; final bool isListed; + final bool isDeleted; + final bool isReadOnly; final DateTime lastModificationTime; final DateTime creationTime; + final DateTime? firstPublishTime; final int visits; final int rating; final int randomOrder; - final List manifest; - final List resoniteDBManifest; - final String url; - final bool isValidOwnerId; - final bool isValidRecordId; + final String? rootRecordId; + final List assetManifest; Record({ required this.id, - required this.combinedRecordId, - required this.isSynced, - required this.fetchedOn, required this.path, required this.ownerId, required this.assetUri, @@ -120,75 +94,27 @@ class Record { required this.isListed, required this.isForPatreons, required this.lastModificationTime, - required this.resoniteDBManifest, - required this.lastModifyingUserId, - required this.lastModifyingMachineId, required this.creationTime, - required this.manifest, - required this.url, - required this.isValidOwnerId, - required this.isValidRecordId, - required this.globalVersion, - required this.localVersion, + required this.assetManifest, + required this.version, required this.visits, required this.rating, required this.randomOrder, + required this.ownerName, + required this.isDeleted, + required this.isReadOnly, + required this.firstPublishTime, + required this.rootRecordId, }) : formattedName = FormatNode.fromText(name), formattedDescription = FormatNode.fromText(description); - factory Record.fromRequiredData({ - required RecordType recordType, - required String userId, - required String machineId, - required String assetUri, - required String filename, - required String thumbnailUri, - required List digests, - List? extraTags, - }) { - final combinedRecordId = RecordId(id: Record.generateId(), ownerId: userId, isValid: true); - return Record( - id: combinedRecordId.id.toString(), - combinedRecordId: combinedRecordId, - assetUri: assetUri, - name: filename, - tags: ([filename, "message_item", "message_id:${Message.generateId()}", "recon"] + (extraTags ?? [])).unique(), - recordType: recordType, - thumbnailUri: thumbnailUri, - isPublic: false, - isForPatreons: false, - isListed: false, - resoniteDBManifest: digests.map((e) => e.asset).toList(), - globalVersion: 0, - localVersion: 1, - lastModifyingUserId: userId, - lastModifyingMachineId: machineId, - lastModificationTime: DateTime.now().toUtc(), - creationTime: DateTime.now().toUtc(), - ownerId: userId, - isSynced: false, - fetchedOn: DateTimeX.one, - path: '', - description: '', - manifest: digests.map((e) => e.dbUri).toList(), - url: "resrec:///$userId/${combinedRecordId.id}", - isValidOwnerId: true, - isValidRecordId: true, - visits: 0, - rating: 0, - randomOrder: 0, - ); - } - factory Record.fromMap(Map map) { return Record( id: map["id"] ?? "0", - combinedRecordId: RecordId.fromMap(map["combinedRecordId"]), ownerId: map["ownerId"] ?? "", assetUri: map["assetUri"] ?? "", - globalVersion: map["globalVersion"] ?? 0, - localVersion: map["localVersion"] ?? 0, name: map["name"] ?? "", + version: RecordVersion.fromMap(map["version"]), description: map["description"] ?? "", tags: (map["tags"] as List? ?? []).map((e) => e.toString()).toList(), recordType: RecordType.fromName(map["recordType"]), @@ -196,21 +122,61 @@ class Record { isPublic: map["isPublic"] ?? false, isForPatreons: map["isForPatreons"] ?? false, isListed: map["isListed"] ?? false, - lastModificationTime: DateTime.tryParse(map["lastModificationTime"]) ?? DateTimeX.epoch, - resoniteDBManifest: (map["resoniteDBManifest"] as List? ?? []).map((e) => ResoniteDBAsset.fromMap(e)).toList(), - lastModifyingUserId: map["lastModifyingUserId"] ?? "", - lastModifyingMachineId: map["lastModifyingMachineId"] ?? "", - creationTime: DateTime.tryParse(map["lastModificationTime"]) ?? DateTimeX.epoch, - isSynced: map["isSynced"] ?? false, - fetchedOn: DateTime.tryParse(map["fetchedOn"] ?? "") ?? DateTimeX.epoch, + lastModificationTime: DateTime.tryParse(map["lastModificationTime"] ?? "") ?? DateTimeX.epoch, + assetManifest: (map["assetManifest"] as List? ?? []).map((e) => AssetManifest.fromMap(e)).toList(), + creationTime: DateTime.tryParse(map["lastModificationTime"] ?? "") ?? DateTimeX.epoch, path: map["path"] ?? "", - manifest: (map["resoniteDBManifest"] as List? ?? []).map((e) => e.toString()).toList(), - url: map["url"] ?? "", - isValidOwnerId: map["isValidOwnerId"] == "true", - isValidRecordId: map["isValidRecordId"] == "true", visits: map["visits"] ?? 0, rating: map["rating"] ?? 0, randomOrder: map["randomOrder"] ?? 0, + ownerName: map["ownerName"], + isDeleted: map["isDeleted"] ?? false, + isReadOnly: map["isReadOnly"] ?? false, + firstPublishTime: DateTime.tryParse(map["firstPublishTime"] ?? ""), + rootRecordId: map["rootRecordId"], + ); + } + + factory Record.local({ + required String name, + required RecordType recordType, + required String ownerId, + required List assetManifest, + String assetUri = "", + List tags = const [], + String? lastModifyingMachineId, + }) { + final now = DateTime.now(); + return Record( + id: generateId(), + path: null, + ownerId: ownerId, + assetUri: assetUri, + name: name, + description: null, + tags: tags, + recordType: recordType, + thumbnailUri: null, + isPublic: false, + isListed: false, + isForPatreons: false, + lastModificationTime: now, + creationTime: now, + assetManifest: assetManifest, + version: RecordVersion( + globalVersion: 0, + localVersion: 1, + lastModifyingUserId: ownerId, + lastModifyingMachineId: lastModifyingMachineId, + ), + visits: 0, + rating: 0, + randomOrder: 0, + ownerName: null, + isDeleted: false, + isReadOnly: false, + firstPublishTime: null, + rootRecordId: null, ); } @@ -255,100 +221,87 @@ class Record { Record copyWith({ String? id, String? ownerId, - String? recordId, String? assetUri, - int? globalVersion, - int? localVersion, + RecordVersion? version, String? name, - String? description, - List? tags, + String? Function()? description, RecordType? recordType, - String? thumbnailUri, + String? Function()? ownerName, + List? tags, + String? Function()? path, + String? Function()? thumbnailUri, bool? isPublic, bool? isForPatreons, bool? isListed, bool? isDeleted, + bool? isReadOnly, DateTime? lastModificationTime, - List? resoniteDBManifest, - String? lastModifyingUserId, - String? lastModifyingMachineId, DateTime? creationTime, - RecordId? combinedRecordId, - bool? isSynced, - DateTime? fetchedOn, - String? path, - List? manifest, - String? url, - bool? isValidOwnerId, - bool? isValidRecordId, + DateTime? Function()? firstPublishTime, int? visits, int? rating, int? randomOrder, - }) { - return Record( - id: id ?? this.id, - ownerId: ownerId ?? this.ownerId, - assetUri: assetUri ?? this.assetUri, - globalVersion: globalVersion ?? this.globalVersion, - localVersion: localVersion ?? this.localVersion, - name: name ?? this.name, - description: description ?? this.description, - tags: tags ?? this.tags, - recordType: recordType ?? this.recordType, - thumbnailUri: thumbnailUri ?? this.thumbnailUri, - isPublic: isPublic ?? this.isPublic, - isForPatreons: isForPatreons ?? this.isForPatreons, - isListed: isListed ?? this.isListed, - lastModificationTime: lastModificationTime ?? this.lastModificationTime, - resoniteDBManifest: resoniteDBManifest ?? this.resoniteDBManifest, - lastModifyingUserId: lastModifyingUserId ?? this.lastModifyingUserId, - lastModifyingMachineId: lastModifyingMachineId ?? this.lastModifyingMachineId, - creationTime: creationTime ?? this.creationTime, - combinedRecordId: combinedRecordId ?? this.combinedRecordId, - isSynced: isSynced ?? this.isSynced, - fetchedOn: fetchedOn ?? this.fetchedOn, - path: path ?? this.path, - manifest: manifest ?? this.manifest, - url: url ?? this.url, - isValidOwnerId: isValidOwnerId ?? this.isValidOwnerId, - isValidRecordId: isValidRecordId ?? this.isValidRecordId, - visits: visits ?? this.visits, - rating: rating ?? this.rating, - randomOrder: randomOrder ?? this.randomOrder, - ); - } + List? assetManifest, + String? Function()? rootRecordId, + }) => + Record( + id: id ?? this.id, + path: path == null ? this.path : path(), + ownerId: ownerId ?? this.ownerId, + assetUri: assetUri ?? this.assetUri, + name: name ?? this.name, + description: description == null ? this.description : description(), + tags: tags ?? this.tags, + recordType: recordType ?? this.recordType, + thumbnailUri: thumbnailUri == null ? this.thumbnailUri : thumbnailUri(), + isPublic: isPublic ?? this.isPublic, + isListed: isListed ?? this.isListed, + isForPatreons: isForPatreons ?? this.isForPatreons, + lastModificationTime: lastModificationTime ?? this.lastModificationTime, + creationTime: creationTime ?? this.creationTime, + assetManifest: assetManifest ?? this.assetManifest, + version: version ?? this.version, + visits: visits ?? this.visits, + rating: rating ?? this.rating, + randomOrder: randomOrder ?? this.randomOrder, + ownerName: ownerName == null ? this.ownerName : ownerName(), + isDeleted: isDeleted ?? this.isDeleted, + isReadOnly: isReadOnly ?? this.isReadOnly, + firstPublishTime: firstPublishTime == null ? this.firstPublishTime : firstPublishTime(), + rootRecordId: rootRecordId == null ? this.rootRecordId : rootRecordId(), + ); Map toMap() { return { "id": id, "ownerId": ownerId, "assetUri": assetUri, - "globalVersion": globalVersion, - "localVersion": localVersion, + "version": version.toMap(), "name": name, - "description": description.asNullable, + "description": description?.asNullable, "tags": tags, "recordType": recordType.name, - "thumbnailUri": thumbnailUri.asNullable, + "thumbnailUri": thumbnailUri?.asNullable, "isPublic": isPublic, "isForPatreons": isForPatreons, "isListed": isListed, "lastModificationTime": lastModificationTime.toUtc().toIso8601String(), - "resoniteDBManifest": resoniteDBManifest.map((e) => e.toMap()).toList(), - "lastModifyingUserId": lastModifyingUserId, - "lastModifyingMachineId": lastModifyingMachineId, + "assetManifest": assetManifest.map((e) => e.toMap()).toList(), "creationTime": creationTime.toUtc().toIso8601String(), - "combinedRecordId": combinedRecordId.toMap(), - "isSynced": isSynced, - "fetchedOn": fetchedOn.toUtc().toIso8601String(), - "path": path.asNullable, - "manifest": manifest, - "url": url, - "isValidOwnerId": isValidOwnerId, - "isValidRecordId": isValidRecordId, + "path": path?.asNullable, "visits": visits, "rating": rating, "randomOrder": randomOrder, + "ownerName": ownerName, + "isDeleted": isDeleted, + "isReadOnly": isReadOnly, + "firstPublishTime": firstPublishTime?.toUtc().toIso8601String(), + "rootRecordId": rootRecordId, + "submissions": null, // no idea what this is yet + "migrationMetadata": null, // don't need to care about this + "IsValidOwnerId": true, + "IsValidRecordId": true, + "neosDBmanifest": null, // legacy stuff I assume }; } diff --git a/lib/models/records/record_version.dart b/lib/models/records/record_version.dart new file mode 100644 index 0000000..a0a98d7 --- /dev/null +++ b/lib/models/records/record_version.dart @@ -0,0 +1,27 @@ +class RecordVersion { + final int globalVersion; + final int localVersion; + final String? lastModifyingUserId; + final String? lastModifyingMachineId; + + const RecordVersion({ + required this.globalVersion, + required this.localVersion, + required this.lastModifyingUserId, + required this.lastModifyingMachineId, + }); + + factory RecordVersion.fromMap(Map map) => RecordVersion( + globalVersion: map['globalVersion'], + localVersion: map['localVersion'], + lastModifyingUserId: map['lastModifyingUserId'], + lastModifyingMachineId: map['lastModifyingMachineId'], + ); + + Map toMap() => { + "globalVersion": globalVersion, + "localVersion": localVersion, + "lastModifyingUserId": lastModifyingUserId, + "lastModifyingMachineId": lastModifyingMachineId, + }; +} diff --git a/lib/widgets/inventory/inventory_browser_app_bar.dart b/lib/widgets/inventory/inventory_browser_app_bar.dart index 861ebba..8ef5642 100644 --- a/lib/widgets/inventory/inventory_browser_app_bar.dart +++ b/lib/widgets/inventory/inventory_browser_app_bar.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'dart:ui'; import 'package:background_downloader/background_downloader.dart'; +import 'package:collection/collection.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -144,7 +145,7 @@ class _InventoryBrowserAppBarState extends State { if (iClient.selectedRecordCount == 1 && ((iClient.selectedRecords.firstOrNull?.isLink ?? false) || (iClient.selectedRecords.firstOrNull?.isItem ?? false))) IconButton( onPressed: () { - Share.share(iClient.selectedRecords.first.assetUri); + SharePlus.instance.share(ShareParams(text: iClient.selectedRecords.first.assetUri)); }, icon: const Icon(Icons.share), ), @@ -183,7 +184,7 @@ class _InventoryBrowserAppBarState extends State { }, leading: const Icon(Icons.image), title: Text( - "Thumbnail${iClient.selectedRecordCount != 1 ? "s" : ""} (${thumbUris.map(extension).toList().unique().join(", ")})", + "Thumbnail${iClient.selectedRecordCount != 1 ? "s" : ""} (${thumbUris.nonNulls.map(extension).toList().unique().join(", ")})", ), ), ], @@ -217,7 +218,8 @@ class _InventoryBrowserAppBarState extends State { for (final record in selectedRecords) { final uri = selectedUris == thumbUris ? record.thumbnailUri : record.assetUri; - final filename = "${record.id.split("-")[1]}-${record.formattedName}${extension(uri)}"; + final filename = + "${record.id.split("-")[1]}-${record.formattedName}${extension(uri ?? "")}"; try { final downloadTask = DownloadTask( url: Aux.resdbToHttp(uri), @@ -229,7 +231,9 @@ class _InventoryBrowserAppBarState extends State { final downloadStatus = await FileDownloader().download(downloadTask); if (downloadStatus.status == TaskStatus.complete) { final tempDirectory = await _tempDirectoryFuture; - final file = File("${tempDirectory.path}/${record.id.split("-")[1]}-${record.formattedName}${extension(uri)}"); + final file = File( + "${tempDirectory.path}/${record.id.split("-")[1]}-${record.formattedName}${extension(uri ?? "")}", + ); if (file.existsSync()) { final newFile = File("$directory/$filename"); await file.copy(newFile.absolute.path); diff --git a/lib/widgets/messages/message_asset.dart b/lib/widgets/messages/message_asset.dart index 597a615..d18b1c8 100644 --- a/lib/widgets/messages/message_asset.dart +++ b/lib/widgets/messages/message_asset.dart @@ -22,6 +22,7 @@ class MessageAsset extends StatelessWidget { final formattedName = FormatNode.fromText(content["name"]); return Container( constraints: const BoxConstraints(maxWidth: 300), + padding: const EdgeInsets.all(8.0), child: Column( children: [ SizedBox( diff --git a/lib/widgets/messages/message_input_bar.dart b/lib/widgets/messages/message_input_bar.dart index 9b6d030..ab73f50 100644 --- a/lib/widgets/messages/message_input_bar.dart +++ b/lib/widgets/messages/message_input_bar.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -5,6 +6,8 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:recon/apis/record_api.dart'; import 'package:recon/auxiliary.dart'; @@ -16,6 +19,7 @@ import 'package:recon/models/message.dart'; import 'package:recon/models/users/friend.dart'; import 'package:recon/widgets/messages/message_attachment_list.dart'; import 'package:record/record.dart'; +import 'package:uuid/uuid.dart'; class MessageInputBar extends StatefulWidget { const MessageInputBar({this.disabled = false, required this.recipient, this.onMessageSent, super.key}); @@ -68,21 +72,11 @@ class _MessageInputBarState extends State { mClient.sendMessage(message); } - Future 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, - ); + Future sendImageMessage(ApiClient client, MessagingClient mClient, File file, String machineId, void Function(double progress) progressCallback) async { + final msgId = Message.generateId(); + final record = await RecordApi.uploadImage(client, image: file, machineId: machineId, messageId: msgId, progressCallback: progressCallback); final message = Message( - id: record.extractMessageId() ?? Message.generateId(), + id: msgId, recipientId: widget.recipient.contactUserId, senderId: client.userId, type: MessageType.object, @@ -93,21 +87,11 @@ class _MessageInputBarState extends State { mClient.sendMessage(message); } - Future sendVoiceMessage( - ApiClient client, - MessagingClient mClient, - File file, - String machineId, - void Function(double progress) progressCallback, - ) async { - final record = await RecordApi.uploadVoiceClip( - client, - voiceClip: file, - machineId: machineId, - progressCallback: progressCallback, - ); + Future sendVoiceMessage(ApiClient client, MessagingClient mClient, File file, String machineId, void Function(double progress) progressCallback) async { + final msgId = Message.generateId(); + final record = await RecordApi.uploadVoiceClip(client, voiceClip: file, machineId: machineId, messageId: msgId, progressCallback: progressCallback); final message = Message( - id: record.extractMessageId() ?? Message.generateId(), + id: msgId, recipientId: widget.recipient.contactUserId, senderId: client.userId, type: MessageType.sound, @@ -118,19 +102,8 @@ class _MessageInputBarState extends State { mClient.sendMessage(message); } - Future sendRawFileMessage( - ApiClient client, - MessagingClient mClient, - File file, - String machineId, - void Function(double progress) progressCallback, - ) async { - final record = await RecordApi.uploadRawFile( - client, - file: file, - machineId: machineId, - progressCallback: progressCallback, - ); + Future sendRawFileMessage(ApiClient client, MessagingClient mClient, File file, String machineId, void Function(double progress) progressCallback) async { + final record = await RecordApi.uploadRawFile(client, file: file, machineId: machineId, progressCallback: progressCallback); final message = Message( id: record.extractMessageId() ?? Message.generateId(), recipientId: widget.recipient.contactUserId, @@ -148,7 +121,6 @@ class _MessageInputBarState extends State { _isSending = true; _sendProgress = 0; _attachmentPickerOpen = false; - _loadedFiles.clear(); }); final cHolder = ClientHolder.of(context); final mClient = Provider.of(context, listen: false); @@ -175,9 +147,7 @@ class _MessageInputBarState extends State { mClient, file.$2, settings.machineId.valueOrDefault, - (progress) => setState( - () => _sendProgress = totalProgress + progress * 1 / toSend.length, - ), + (progress) => setState(() => _sendProgress = totalProgress + progress * 1 / toSend.length), ); } } @@ -257,7 +227,14 @@ class _MessageInputBarState extends State { }); if (await _recorder.isRecording()) { - final recording = await _recorder.stop(); + String? recording; + try { + recording = await _recorder.stop(); + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Failed to finalize recording: $e"))); + } + } if (recording == null) return; final file = File(recording); @@ -266,11 +243,22 @@ class _MessageInputBarState extends State { _sendProgress = 0; }); final apiClient = cHolder.apiClient; - await sendVoiceMessage(apiClient, mClient, file, cHolder.settingsClient.currentSettings.machineId.valueOrDefault, (progress) { + try { + await sendVoiceMessage(apiClient, mClient, file, cHolder.settingsClient.currentSettings.machineId.valueOrDefault, (progress) { + setState(() { + _sendProgress = progress; + }); + }); + } catch (e, s) { + FlutterError.reportError(FlutterErrorDetails(exception: e, stack: s)); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Failed to send voice message: $e"))); + } setState(() { - _sendProgress = progress; + _sendProgress = null; + _isSending = false; }); - }); + } setState(() { _isSending = false; _sendProgress = null; @@ -311,31 +299,26 @@ class _MessageInputBarState extends State { children: [ if (_isSending && _sendProgress != null) LinearProgressIndicator(value: _sendProgress), DecoratedBox( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerHighest, - ), + decoration: BoxDecoration(color: Theme.of(context).colorScheme.surfaceContainerHighest), child: AnimatedSwitcher( duration: const Duration(milliseconds: 200), switchInCurve: Curves.easeOut, switchOutCurve: Curves.easeOut, - transitionBuilder: (child, animation) => SizeTransition( - sizeFactor: animation, - child: child, - ), + transitionBuilder: (child, animation) => SizeTransition(sizeFactor: animation, child: child), child: switch ((_attachmentPickerOpen, _loadedFiles)) { - (true, []) => Row( + (true, []) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Row( key: const ValueKey("attachment-picker"), children: [ TextButton.icon( onPressed: _isSending ? null : () async { - final result = await FilePicker.pickFiles(type: FileType.image, allowMultiple: true); + final result = await FilePicker.pickFiles(type: FileType.image); if (result != null) { setState(() { - _loadedFiles.addAll( - result.files.map((e) => e.path != null ? (FileType.image, File(e.path!)) : null).nonNulls, - ); + _loadedFiles.addAll(result.files.map((e) => e.path != null ? (FileType.image, File(e.path!)) : null).nonNulls); }); } }, @@ -371,12 +354,10 @@ class _MessageInputBarState extends State { onPressed: _isSending ? null : () async { - final result = await FilePicker.pickFiles(type: FileType.any, allowMultiple: true); + final result = await FilePicker.pickFiles(type: FileType.any); if (result != null) { setState(() { - _loadedFiles.addAll( - result.files.map((e) => e.path != null ? (FileType.any, File(e.path!)) : null).nonNulls, - ); + _loadedFiles.addAll(result.files.map((e) => e.path != null ? (FileType.any, File(e.path!)) : null).nonNulls); }); } }, @@ -385,16 +366,17 @@ class _MessageInputBarState extends State { ), ], ), + ), (false, []) => null, (_, _) => MessageAttachmentList( - disabled: _isSending, - initialFiles: _loadedFiles, - onChange: (loadedFiles) => setState(() { - _loadedFiles - ..clear() - ..addAll(loadedFiles); - }), - ), + disabled: _isSending, + initialFiles: _loadedFiles, + onChange: (loadedFiles) => setState(() { + _loadedFiles + ..clear() + ..addAll(loadedFiles); + }), + ), }, ), ), @@ -404,77 +386,63 @@ class _MessageInputBarState extends State { duration: const Duration(milliseconds: 200), transitionBuilder: (child, animation) => FadeTransition( opacity: animation, - child: RotationTransition( - turns: Tween(begin: 0.6, end: 1).animate(animation), - child: child, - ), + child: RotationTransition(turns: Tween(begin: 0.6, end: 1).animate(animation), child: child), ), child: switch ((_attachmentPickerOpen, _isRecording)) { (_, true) => IconButton( - onPressed: () {}, - icon: Icon( - Icons.delete, - color: _recordingCancelled ? Theme.of(context).colorScheme.error : null, - ), - ), + onPressed: () {}, + icon: Icon(Icons.delete, color: _recordingCancelled ? Theme.of(context).colorScheme.error : null), + ), (false, _) => IconButton( - key: const ValueKey("add-attachment-icon"), - onPressed: _isSending - ? null - : () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Sorry, this feature is not yet available")), - ); - return; - // setState(() { - // _attachmentPickerOpen = true; - // }); - }, - icon: const Icon( - Icons.attach_file, - ), - ), + key: const ValueKey("add-attachment-icon"), + onPressed: _isSending + ? null + : () { + setState(() { + _attachmentPickerOpen = true; + }); + }, + icon: const Icon(Icons.attach_file), + ), (true, _) => IconButton( - key: const ValueKey("remove-attachment-icon"), - onPressed: _isSending - ? null - : () async { - if (_loadedFiles.isNotEmpty) { - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Remove all attachments"), - content: const Text("This will remove all attachments, are you sure?"), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("No"), - ), - TextButton( - onPressed: () { - setState(() { - _loadedFiles.clear(); - _attachmentPickerOpen = false; - }); - Navigator.of(context).pop(); - }, - child: const Text("Yes"), - ), - ], - ), - ); - } else { - setState(() { - _attachmentPickerOpen = false; - }); - } - }, - icon: const Icon( - Icons.close, - ), - ), + key: const ValueKey("remove-attachment-icon"), + onPressed: _isSending + ? null + : () async { + if (_loadedFiles.isNotEmpty) { + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text("Remove all attachments"), + content: const Text("This will remove all attachments, are you sure?"), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("No"), + ), + TextButton( + onPressed: () { + setState(() { + _loadedFiles.clear(); + _attachmentPickerOpen = false; + }); + Navigator.of(context).pop(); + }, + child: const Text("Yes"), + ), + ], + ), + ); + } else { + setState(() { + _attachmentPickerOpen = false; + }); + } + }, + icon: const Icon(Icons.close), + ), }, ), Expanded( @@ -506,10 +474,7 @@ class _MessageInputBarState extends State { contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), fillColor: Colors.black26, filled: true, - border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(24), - ), + border: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(24)), ), ), AnimatedSwitcher( @@ -517,10 +482,7 @@ class _MessageInputBarState extends State { transitionBuilder: (child, animation) => FadeTransition( opacity: animation, child: SlideTransition( - position: Tween( - begin: const Offset(0, .2), - end: Offset.zero, - ).animate(animation), + position: Tween(begin: const Offset(0, .2), end: Offset.zero).animate(animation), child: child, ), ), @@ -531,16 +493,10 @@ class _MessageInputBarState extends State { ? Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - const SizedBox( - width: 8, - ), + const SizedBox(width: 8), const Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), - child: Icon( - Icons.cancel, - color: Colors.red, - size: 16, - ), + child: Icon(Icons.cancel, color: Colors.red, size: 16), ), Text("Cancel Recording", style: Theme.of(context).textTheme.titleMedium), ], @@ -548,24 +504,15 @@ class _MessageInputBarState extends State { : Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - const SizedBox( - width: 8, - ), + const SizedBox(width: 8), const Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), - child: Icon( - Icons.circle, - color: Colors.red, - size: 16, - ), + child: Icon(Icons.circle, color: Colors.red, size: 16), ), StreamBuilder( stream: _recordingDurationStream(), builder: (context, snapshot) { - return Text( - "Recording: ${snapshot.data?.format()}", - style: Theme.of(context).textTheme.titleMedium, - ); + return Text("Recording: ${snapshot.data?.format()}", style: Theme.of(context).textTheme.titleMedium); }, ), ], @@ -581,10 +528,7 @@ class _MessageInputBarState extends State { duration: const Duration(milliseconds: 200), transitionBuilder: (child, animation) => FadeTransition( opacity: animation, - child: RotationTransition( - turns: Tween(begin: 0.5, end: 1).animate(animation), - child: child, - ), + child: RotationTransition(turns: Tween(begin: 0.5, end: 1).animate(animation), child: child), ), child: _currentText.trim().isNotEmpty || _loadedFiles.isNotEmpty ? IconButton( @@ -601,40 +545,33 @@ class _MessageInputBarState extends State { onTapDown: widget.disabled ? null : (_) async { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Sorry, this feature is not yet available")), - ); - return; - // HapticFeedback.vibrate(); - // final hadToAsk = - // await Permission.microphone.isDenied; - // final hasPermission = - // !await _recorder.hasPermission(); - // if (hasPermission) { - // if (context.mounted) { - // ScaffoldMessenger.of(context) - // .showSnackBar(const SnackBar( - // content: Text( - // "No permission to record audio."), - // )); - // } - // return; - // } - // if (hadToAsk) { - // // We had to ask for permissions so the user removed their finger from the record button. - // return; - // } + unawaited(HapticFeedback.vibrate()); + bool hadToAsk; + try { + hadToAsk = await Permission.microphone.isDenied; + } catch (_) { + hadToAsk = false; + } + final hasPermission = !await _recorder.hasPermission(); + if (hasPermission) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("No permission to record audio."))); + } + return; + } + if (hadToAsk) { + // We had to ask for permissions so the user removed their finger from the record button. + return; + } - // final dir = await getTemporaryDirectory(); - // await _recorder.start( - // path: "${dir.path}/A-${const Uuid().v4()}.wav", - // const RecordConfig( - // numChannels: 1, - // sampleRate: 44100, - // encoder: AudioEncoder.wav)); - // setState(() { - // _isRecording = true; - // }); + final dir = await getTemporaryDirectory(); + await _recorder.start( + path: "${dir.path}/A-${const Uuid().v4()}.wav", + const RecordConfig(numChannels: 1, sampleRate: 44100, encoder: AudioEncoder.wav), + ); + setState(() { + _isRecording = true; + }); }, child: IconButton( icon: const Icon(Icons.mic_outlined), diff --git a/lib/widgets/worlds/world_view.dart b/lib/widgets/worlds/world_view.dart index 0afcff2..b4b2c22 100644 --- a/lib/widgets/worlds/world_view.dart +++ b/lib/widgets/worlds/world_view.dart @@ -114,10 +114,10 @@ class _WorldViewState extends State { ), Padding( padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 8), - child: widget.world.formattedDescription.isEmpty + child: widget.world.formattedDescription?.isEmpty ?? true ? Text("No description", style: Theme.of(context).textTheme.labelLarge) : FormattedText( - widget.world.formattedDescription, + widget.world.formattedDescription!, style: Theme.of(context).textTheme.labelLarge?.apply(fontStyle: FontStyle.italic), ), ), diff --git a/pubspec.lock b/pubspec.lock index d366f2a..a77d16a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: audio_session - sha256: "7217b229db57cc4dc577a8abb56b7429a5a212b978517a5be578704bfe5e568b" + sha256: f9e7711a0e24ca8b40f5d7ac374c3c6e55016f3157912badd18199cdbbca5c4d url: "https://pub.dev" source: hosted - version: "0.2.3" + version: "0.2.4" background_downloader: dependency: "direct main" description: @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + bson: + dependency: "direct main" + description: + name: bson + sha256: "6e322cdf827db905ca48b018deed1804934840bce8ff6b3c2f5b2f81f99d8344" + url: "https://pub.dev" + source: hosted + version: "5.0.8" cached_network_image: dependency: "direct main" description: @@ -85,10 +93,10 @@ packages: dependency: transitive description: name: camera_android_camerax - sha256: e20c1e92ce6797d9ae9b1db1e09a4c1039a04827d0b24985f5da3840b96948ac + sha256: "50c3fd81228826635af4875330febb193c74a2e12a4d19c1b95e2a67f0017bb3" url: "https://pub.dev" source: hosted - version: "0.7.2+1" + version: "0.7.3" camera_avfoundation: dependency: transitive description: @@ -157,10 +165,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" url: "https://pub.dev" source: hosted - version: "0.3.5+2" + version: "0.3.5+4" crypto: dependency: "direct main" description: @@ -181,10 +189,18 @@ packages: dependency: transitive description: name: dbus - sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" + sha256: "792974a4007974fbc5c1b5433eb2330a9db3e368c3f906253af4c007d0f49a91" url: "https://pub.dev" source: hosted - version: "0.7.14" + version: "0.7.13" + decimal: + dependency: transitive + description: + name: decimal + sha256: fc706a5618b81e5b367b01dd62621def37abc096f2b46a9bd9068b64c1fa36d0 + url: "https://pub.dev" + source: hosted + version: "3.2.4" dynamic_color: dependency: "direct main" description: @@ -193,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.1" + es_compression: + dependency: "direct main" + description: + name: es_compression + sha256: c1ff7af54802631cf5c3942cb67bb99daadcc087f573ca99a9de91002d1a7ece + url: "https://pub.dev" + source: hosted + version: "2.0.15" fake_async: dependency: transitive description: @@ -465,21 +489,21 @@ packages: source: hosted version: "4.1.2" image: - dependency: transitive + dependency: "direct main" description: name: image - sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52" url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.9.1" image_picker: dependency: "direct main" description: name: image_picker - sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" + sha256: d8402284df184bc05f4a2210c6c23983b0720f4cd87cbd05c5390a78af602667 url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.3" image_picker_android: dependency: transitive description: @@ -540,10 +564,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" url: "https://pub.dev" source: hosted - version: "0.20.2" + version: "0.20.3" jni: dependency: transitive description: @@ -564,10 +588,10 @@ packages: dependency: "direct main" description: name: just_audio - sha256: "9694e4734f515f2a052493d1d7e0d6de219ee0427c7c29492e246ff32a219908" + sha256: e60aa97b233ddea025e13dfac59195207f528fa5e9e4a4a49a8c0766701e5fe6 url: "https://pub.dev" source: hosted - version: "0.10.5" + version: "0.10.6" just_audio_media_kit: dependency: "direct main" description: @@ -724,10 +748,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "4bf625947f6c7713ee242296a682e23e44823c09cf9d79e4f1238923c92db852" + sha256: f5c435dc0e0d461e5b32471a870f769b6a1cc46930637efe24fbc535314e78ad url: "https://pub.dev" source: hosted - version: "10.1.0" + version: "10.2.0" package_info_plus_platform_interface: dependency: transitive description: @@ -736,6 +760,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + packages_extensions: + dependency: transitive + description: + name: packages_extensions + sha256: a2e207f3345fc5cbea60155842e71be1c3059b427a61ed1c5d7c41f5d03264e7 + url: "https://pub.dev" + source: hosted + version: "0.1.2" path: dependency: "direct main" description: @@ -772,10 +804,10 @@ packages: dependency: transitive description: name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" path_provider_platform_interface: dependency: transitive description: @@ -880,6 +912,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" + power_extensions: + dependency: transitive + description: + name: power_extensions + sha256: ad4e27d040f80bb7e566d96399b6419343d946d7c37912e1bce56c9f2b32e525 + url: "https://pub.dev" + source: hosted + version: "0.2.4" provider: dependency: "direct main" description: @@ -896,30 +936,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + rational: + dependency: transitive + description: + name: rational + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 + url: "https://pub.dev" + source: hosted + version: "2.2.3" record: dependency: "direct main" description: name: record - sha256: d28ec249ee4af6753a68a7a5077d6a2eee71e38dc61a241724aded53263274ba + sha256: "82539d1372e23cf51375fdfcba084f39912bcbf9a953b75d56596691f8f11c0f" url: "https://pub.dev" source: hosted - version: "7.1.0" + version: "7.1.1" record_android: dependency: transitive description: name: record_android - sha256: "6722be803d8b2a0945112d69cfbda1391970c5cff2cb53c16afd307a6b7ef0c4" + sha256: "28f1108626a190e249b01ffa9f639070e31e5157474b64a5ae380bf36aec9559" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" record_ios: dependency: transitive description: name: record_ios - sha256: de6f660b02c2a909c963d5c3c929fdf100b03cea9e5552599d0e5ae1b7d1a89d + sha256: "21d189f49a598af4697dac4cc9e48389ac0a1fb3e916622b5504a58d9b96313e" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" record_linux: dependency: transitive description: @@ -932,10 +980,10 @@ packages: dependency: transitive description: name: record_macos - sha256: "9c29a7efdace0662db2aa7284873d49f8b48992432db3b5f0d38cbe2da8eecde" + sha256: ced7495abf3d683e8a7dbe8fc96df8ea5722837a26f922b7cb7c5de11661bb46 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" record_platform_interface: dependency: transitive description: @@ -956,18 +1004,18 @@ packages: dependency: transitive description: name: record_web - sha256: "9d2d43162afff63d8608eb09c2982b9c6898dd8fbf5b05395ca7d08305b6db40" + sha256: "7d75ed681b5bf40c3a9b51b6c105fe53705f752284d859d5f030ab5a2bfd49cf" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" record_windows: dependency: transitive description: name: record_windows - sha256: a1db1c0b996acd4161a93cc412b81a2a09a4fd7161c6353f32bf97683003b52f + sha256: "9cedfacee553a02b48977a5e1d74bf08a36cc5647eba56fb83e57da20552f443" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.2" rxdart: dependency: transitive description: @@ -980,18 +1028,18 @@ packages: dependency: transitive description: name: safe_local_storage - sha256: "287ea1f667c0b93cdc127dccc707158e2d81ee59fba0459c31a0c7da4d09c755" + sha256: "7483b3d5e8976f0bd263647c03b96131ee8e43f48b56fa8a8ec459e8515d74b0" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" share_plus: dependency: "direct main" description: name: share_plus - sha256: a857d8b1479250aff6b57a51b2c02d31ca05848d441817c43f1640c885c286c0 + sha256: "9eee8283462d91a7a1c8bdb67d08874abd75a2f8fae3bc0ca033035e375fb3d8" url: "https://pub.dev" source: hosted - version: "13.1.0" + version: "13.2.0" share_plus_platform_interface: dependency: transitive description: @@ -1041,10 +1089,10 @@ packages: dependency: transitive description: name: sqflite_darwin - sha256: "164a5d73ab87a134566057219988bafde837029a64264e61f1f04376ef3cfcd2" + sha256: c86ca18b8f666bbf903924687fe21cc16fc385d086005067e26619ca530bef9f url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.3+1" sqflite_platform_interface: dependency: transitive description: @@ -1113,10 +1161,10 @@ packages: dependency: transitive description: name: timezone - sha256: "784a5e34d2eb62e1326f24d6f600aaaee452eb8ca8ef2f384a59244e292d158b" + sha256: "981d1020d6ef8fe1e7b3de5054e5b25579ae7c403d7734adc508ffc47668e9cb" url: "https://pub.dev" source: hosted - version: "0.11.0" + version: "0.11.1" typed_data: dependency: transitive description: @@ -1305,10 +1353,10 @@ packages: dependency: transitive description: name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "7.0.1" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d38f556..0b72a33 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,10 +16,10 @@ 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: 0.12.2-beta +version: 0.13.0-beta environment: - sdk: ">=3.0.1" + sdk: ">=3.12.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -55,7 +55,7 @@ dependencies: hive: ^2.2.3 hive_flutter: ^1.1.0 file_picker: ^12.0.0-beta.7 - record: ^7.1.0 + record: ^7.1.1 camera: ^0.12.0+1 path_provider: ^2.1.5 crypto: ^3.0.7 @@ -67,6 +67,9 @@ dependencies: just_audio_media_kit: ^2.1.0 media_kit_libs_linux: ^1.2.1 media_kit_libs_windows_audio: ^1.0.9 + es_compression: ^2.0.15 + bson: ^5.0.8 + image: ^4.9.1 dev_dependencies: flutter_test: @@ -92,6 +95,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/images/ + - assets/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/test/widget_test.dart b/test/widget_test.dart index 59c6120..d14923d 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:recon/clients/settings_client.dart'; import 'package:recon/main.dart'; import 'package:recon/models/authentication_data.dart'; @@ -17,6 +18,7 @@ void main() { await tester.pumpWidget(ReCon( settingsClient: SettingsClient(), cachedAuthentication: AuthenticationData.unauthenticated(), + packageInfo: await PackageInfo.fromPlatform(), )); // Verify that our counter starts at 0.