mirror of
https://github.com/immich-app/immich.git
synced 2025-01-16 00:36:47 +01:00
Add information for uploading asset and error indication with error message for each failed upload. (#315)
* Added info box * Fixed upload endpoint doesn't report error status code * Added chip to show update error * Added chip to show failed upload * Add duplication check for upload * Better duplication-checking placement * Remove check for duplicated asset * Added failed backup status route * added page * Display error card with thumbnail * Improved styling * Set thumbnail with better quality * Remove force upload error
This commit is contained in:
parent
357f7d1c31
commit
58ec7553ea
19 changed files with 706 additions and 87 deletions
|
@ -19,7 +19,7 @@ platform :ios do
|
|||
desc "iOS Beta"
|
||||
lane :beta do
|
||||
increment_version_number(
|
||||
version_number: "1.16.1"
|
||||
version_number: "1.17.0"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import 'package:cancellation_token_http/http.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
|
||||
import 'package:immich_mobile/shared/models/server_info.model.dart';
|
||||
|
||||
enum BackUpProgressEnum { idle, inProgress, done }
|
||||
|
||||
class BackUpState extends Equatable {
|
||||
class BackUpState {
|
||||
// enum
|
||||
final BackUpProgressEnum backupProgress;
|
||||
final List<String> allAssetsInDatabase;
|
||||
|
@ -26,6 +27,9 @@ class BackUpState extends Equatable {
|
|||
/// All assets from the selected albums that have been backup
|
||||
final Set<String> selectedAlbumsBackupAssetsIds;
|
||||
|
||||
// Current Backup Asset
|
||||
final CurrentUploadAsset currentUploadAsset;
|
||||
|
||||
const BackUpState({
|
||||
required this.backupProgress,
|
||||
required this.allAssetsInDatabase,
|
||||
|
@ -37,6 +41,7 @@ class BackUpState extends Equatable {
|
|||
required this.excludedBackupAlbums,
|
||||
required this.allUniqueAssets,
|
||||
required this.selectedAlbumsBackupAssetsIds,
|
||||
required this.currentUploadAsset,
|
||||
});
|
||||
|
||||
BackUpState copyWith({
|
||||
|
@ -50,6 +55,7 @@ class BackUpState extends Equatable {
|
|||
Set<AssetPathEntity>? excludedBackupAlbums,
|
||||
Set<AssetEntity>? allUniqueAssets,
|
||||
Set<String>? selectedAlbumsBackupAssetsIds,
|
||||
CurrentUploadAsset? currentUploadAsset,
|
||||
}) {
|
||||
return BackUpState(
|
||||
backupProgress: backupProgress ?? this.backupProgress,
|
||||
|
@ -63,27 +69,47 @@ class BackUpState extends Equatable {
|
|||
allUniqueAssets: allUniqueAssets ?? this.allUniqueAssets,
|
||||
selectedAlbumsBackupAssetsIds:
|
||||
selectedAlbumsBackupAssetsIds ?? this.selectedAlbumsBackupAssetsIds,
|
||||
currentUploadAsset: currentUploadAsset ?? this.currentUploadAsset,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds)';
|
||||
return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds, currentUploadAsset: $currentUploadAsset)';
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props {
|
||||
return [
|
||||
backupProgress,
|
||||
allAssetsInDatabase,
|
||||
progressInPercentage,
|
||||
cancelToken,
|
||||
serverInfo,
|
||||
availableAlbums,
|
||||
selectedBackupAlbums,
|
||||
excludedBackupAlbums,
|
||||
allUniqueAssets,
|
||||
selectedAlbumsBackupAssetsIds,
|
||||
];
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
final collectionEquals = const DeepCollectionEquality().equals;
|
||||
|
||||
return other is BackUpState &&
|
||||
other.backupProgress == backupProgress &&
|
||||
collectionEquals(other.allAssetsInDatabase, allAssetsInDatabase) &&
|
||||
other.progressInPercentage == progressInPercentage &&
|
||||
other.cancelToken == cancelToken &&
|
||||
other.serverInfo == serverInfo &&
|
||||
collectionEquals(other.availableAlbums, availableAlbums) &&
|
||||
collectionEquals(other.selectedBackupAlbums, selectedBackupAlbums) &&
|
||||
collectionEquals(other.excludedBackupAlbums, excludedBackupAlbums) &&
|
||||
collectionEquals(other.allUniqueAssets, allUniqueAssets) &&
|
||||
collectionEquals(other.selectedAlbumsBackupAssetsIds,
|
||||
selectedAlbumsBackupAssetsIds) &&
|
||||
other.currentUploadAsset == currentUploadAsset;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return backupProgress.hashCode ^
|
||||
allAssetsInDatabase.hashCode ^
|
||||
progressInPercentage.hashCode ^
|
||||
cancelToken.hashCode ^
|
||||
serverInfo.hashCode ^
|
||||
availableAlbums.hashCode ^
|
||||
selectedBackupAlbums.hashCode ^
|
||||
excludedBackupAlbums.hashCode ^
|
||||
allUniqueAssets.hashCode ^
|
||||
selectedAlbumsBackupAssetsIds.hashCode ^
|
||||
currentUploadAsset.hashCode;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import 'dart:convert';
|
||||
|
||||
class CheckDuplicateAssetResponse {
|
||||
final bool isExist;
|
||||
CheckDuplicateAssetResponse({
|
||||
required this.isExist,
|
||||
});
|
||||
|
||||
CheckDuplicateAssetResponse copyWith({
|
||||
bool? isExist,
|
||||
}) {
|
||||
return CheckDuplicateAssetResponse(
|
||||
isExist: isExist ?? this.isExist,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
final result = <String, dynamic>{};
|
||||
|
||||
result.addAll({'isExist': isExist});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
factory CheckDuplicateAssetResponse.fromMap(Map<String, dynamic> map) {
|
||||
return CheckDuplicateAssetResponse(
|
||||
isExist: map['isExist'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory CheckDuplicateAssetResponse.fromJson(String source) =>
|
||||
CheckDuplicateAssetResponse.fromMap(json.decode(source));
|
||||
|
||||
@override
|
||||
String toString() => 'CheckDuplicateAssetResponse(isExist: $isExist)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is CheckDuplicateAssetResponse && other.isExist == isExist;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => isExist.hashCode;
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import 'dart:convert';
|
||||
|
||||
class CurrentUploadAsset {
|
||||
final String id;
|
||||
final DateTime createdAt;
|
||||
final String fileName;
|
||||
final String fileType;
|
||||
|
||||
CurrentUploadAsset({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.fileName,
|
||||
required this.fileType,
|
||||
});
|
||||
|
||||
CurrentUploadAsset copyWith({
|
||||
String? id,
|
||||
DateTime? createdAt,
|
||||
String? fileName,
|
||||
String? fileType,
|
||||
}) {
|
||||
return CurrentUploadAsset(
|
||||
id: id ?? this.id,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
fileName: fileName ?? this.fileName,
|
||||
fileType: fileType ?? this.fileType,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
final result = <String, dynamic>{};
|
||||
|
||||
result.addAll({'id': id});
|
||||
result.addAll({'createdAt': createdAt.millisecondsSinceEpoch});
|
||||
result.addAll({'fileName': fileName});
|
||||
result.addAll({'fileType': fileType});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
factory CurrentUploadAsset.fromMap(Map<String, dynamic> map) {
|
||||
return CurrentUploadAsset(
|
||||
id: map['id'] ?? '',
|
||||
createdAt: DateTime.fromMillisecondsSinceEpoch(map['createdAt']),
|
||||
fileName: map['fileName'] ?? '',
|
||||
fileType: map['fileType'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory CurrentUploadAsset.fromJson(String source) =>
|
||||
CurrentUploadAsset.fromMap(json.decode(source));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CurrentUploadAsset(id: $id, createdAt: $createdAt, fileName: $fileName, fileType: $fileType)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is CurrentUploadAsset &&
|
||||
other.id == id &&
|
||||
other.createdAt == createdAt &&
|
||||
other.fileName == fileName &&
|
||||
other.fileType == fileType;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^
|
||||
createdAt.hashCode ^
|
||||
fileName.hashCode ^
|
||||
fileType.hashCode;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
class ErrorUploadAsset extends Equatable {
|
||||
final String id;
|
||||
final DateTime createdAt;
|
||||
final String fileName;
|
||||
final String fileType;
|
||||
final AssetEntity asset;
|
||||
final String errorMessage;
|
||||
|
||||
const ErrorUploadAsset({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.fileName,
|
||||
required this.fileType,
|
||||
required this.asset,
|
||||
required this.errorMessage,
|
||||
});
|
||||
|
||||
ErrorUploadAsset copyWith({
|
||||
String? id,
|
||||
DateTime? createdAt,
|
||||
String? fileName,
|
||||
String? fileType,
|
||||
AssetEntity? asset,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return ErrorUploadAsset(
|
||||
id: id ?? this.id,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
fileName: fileName ?? this.fileName,
|
||||
fileType: fileType ?? this.fileType,
|
||||
asset: asset ?? this.asset,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ErrorUploadAsset(id: $id, createdAt: $createdAt, fileName: $fileName, fileType: $fileType, asset: $asset, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props {
|
||||
return [
|
||||
id,
|
||||
fileName,
|
||||
fileType,
|
||||
errorMessage,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -5,7 +5,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart';
|
||||
import 'package:immich_mobile/modules/backup/services/backup.service.dart';
|
||||
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
|
@ -14,8 +17,12 @@ import 'package:immich_mobile/shared/services/server_info.service.dart';
|
|||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
BackupNotifier(this._backupService, this._serverInfoService, this._authState)
|
||||
: super(
|
||||
BackupNotifier(
|
||||
this._backupService,
|
||||
this._serverInfoService,
|
||||
this._authState,
|
||||
this.ref,
|
||||
) : super(
|
||||
BackUpState(
|
||||
backupProgress: BackUpProgressEnum.idle,
|
||||
allAssetsInDatabase: const [],
|
||||
|
@ -35,6 +42,12 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
excludedBackupAlbums: const {},
|
||||
allUniqueAssets: const {},
|
||||
selectedAlbumsBackupAssetsIds: const {},
|
||||
currentUploadAsset: CurrentUploadAsset(
|
||||
id: '...',
|
||||
createdAt: DateTime.parse('2020-10-04'),
|
||||
fileName: '...',
|
||||
fileType: '...',
|
||||
),
|
||||
),
|
||||
) {
|
||||
getBackupInfo();
|
||||
|
@ -43,6 +56,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
final BackupService _backupService;
|
||||
final ServerInfoService _serverInfoService;
|
||||
final AuthenticationState _authState;
|
||||
final Ref ref;
|
||||
|
||||
///
|
||||
/// UI INTERACTION
|
||||
|
@ -235,8 +249,11 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
/// and then update the UI according to those information
|
||||
///
|
||||
Future<void> getBackupInfo() async {
|
||||
await _getBackupAlbumsInfo();
|
||||
await _updateServerInfo();
|
||||
await Future.wait([
|
||||
_getBackupAlbumsInfo(),
|
||||
_updateServerInfo(),
|
||||
]);
|
||||
|
||||
await _updateBackupAssetCount();
|
||||
}
|
||||
|
||||
|
@ -287,13 +304,27 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
|
||||
// Perform Backup
|
||||
state = state.copyWith(cancelToken: CancellationToken());
|
||||
_backupService.backupAsset(assetsWillBeBackup, state.cancelToken,
|
||||
_onAssetUploaded, _onUploadProgress);
|
||||
_backupService.backupAsset(
|
||||
assetsWillBeBackup,
|
||||
state.cancelToken,
|
||||
_onAssetUploaded,
|
||||
_onUploadProgress,
|
||||
_onSetCurrentBackupAsset,
|
||||
_onBackupError,
|
||||
);
|
||||
} else {
|
||||
PhotoManager.openSetting();
|
||||
}
|
||||
}
|
||||
|
||||
void _onBackupError(ErrorUploadAsset errorAssetInfo) {
|
||||
ref.watch(errorBackupListProvider.notifier).add(errorAssetInfo);
|
||||
}
|
||||
|
||||
void _onSetCurrentBackupAsset(CurrentUploadAsset currentUploadAsset) {
|
||||
state = state.copyWith(currentUploadAsset: currentUploadAsset);
|
||||
}
|
||||
|
||||
void cancelBackup() {
|
||||
state.cancelToken.cancel();
|
||||
state = state.copyWith(
|
||||
|
@ -375,5 +406,6 @@ final backupProvider =
|
|||
ref.watch(backupServiceProvider),
|
||||
ref.watch(serverInfoServiceProvider),
|
||||
ref.watch(authenticationProvider),
|
||||
ref,
|
||||
);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
|
||||
|
||||
class ErrorBackupListNotifier extends StateNotifier<Set<ErrorUploadAsset>> {
|
||||
ErrorBackupListNotifier() : super({});
|
||||
|
||||
add(ErrorUploadAsset errorAsset) {
|
||||
state = state.union({errorAsset});
|
||||
}
|
||||
|
||||
remove(ErrorUploadAsset errorAsset) {
|
||||
state = state.difference({errorAsset});
|
||||
}
|
||||
|
||||
empty() {
|
||||
state = {};
|
||||
}
|
||||
}
|
||||
|
||||
final errorBackupListProvider =
|
||||
StateNotifierProvider<ErrorBackupListNotifier, Set<ErrorUploadAsset>>(
|
||||
(ref) => ErrorBackupListNotifier(),
|
||||
);
|
|
@ -7,6 +7,9 @@ import 'package:flutter/material.dart';
|
|||
import 'package:hive/hive.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/check_duplicate_asset_response.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
|
||||
import 'package:immich_mobile/shared/services/network.service.dart';
|
||||
import 'package:immich_mobile/shared/models/device_info.model.dart';
|
||||
import 'package:immich_mobile/utils/files_helper.dart';
|
||||
|
@ -20,6 +23,7 @@ final backupServiceProvider =
|
|||
|
||||
class BackupService {
|
||||
final NetworkService _networkService;
|
||||
|
||||
BackupService(this._networkService);
|
||||
|
||||
Future<List<String>> getDeviceBackupAsset() async {
|
||||
|
@ -32,17 +36,40 @@ class BackupService {
|
|||
return result.cast<String>();
|
||||
}
|
||||
|
||||
Future<bool> checkDuplicateAsset(String deviceAssetId) async {
|
||||
String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
|
||||
|
||||
try {
|
||||
Response response =
|
||||
await _networkService.postRequest(url: "asset/check", data: {
|
||||
"deviceId": deviceId,
|
||||
"deviceAssetId": deviceAssetId,
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var result = CheckDuplicateAssetResponse.fromJson(response.toString());
|
||||
|
||||
return result.isExist;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
backupAsset(
|
||||
Set<AssetEntity> assetList,
|
||||
http.CancellationToken cancelToken,
|
||||
Function(String, String) singleAssetDoneCb,
|
||||
Function(int, int) uploadProgress) async {
|
||||
Set<AssetEntity> assetList,
|
||||
http.CancellationToken cancelToken,
|
||||
Function(String, String) singleAssetDoneCb,
|
||||
Function(int, int) uploadProgressCb,
|
||||
Function(CurrentUploadAsset) setCurrentUploadAssetCb,
|
||||
Function(ErrorUploadAsset) errorCb,
|
||||
) async {
|
||||
String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
|
||||
String savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey);
|
||||
File? file;
|
||||
|
||||
http.MultipartFile? thumbnailUploadData;
|
||||
|
||||
for (var entity in assetList) {
|
||||
try {
|
||||
if (entity.type == AssetType.video) {
|
||||
|
@ -74,7 +101,7 @@ class BackupService {
|
|||
var req = MultipartRequest(
|
||||
'POST', Uri.parse('$savedEndpoint/asset/upload'),
|
||||
onProgress: ((bytes, totalBytes) =>
|
||||
uploadProgress(bytes, totalBytes)));
|
||||
uploadProgressCb(bytes, totalBytes)));
|
||||
req.headers["Authorization"] = "Bearer ${box.get(accessTokenKey)}";
|
||||
|
||||
req.fields['deviceAssetId'] = entity.id;
|
||||
|
@ -88,10 +115,35 @@ class BackupService {
|
|||
|
||||
req.files.add(assetRawUploadData);
|
||||
|
||||
var res = await req.send(cancellationToken: cancelToken);
|
||||
setCurrentUploadAssetCb(
|
||||
CurrentUploadAsset(
|
||||
id: entity.id,
|
||||
createdAt: entity.createDateTime,
|
||||
fileName: originalFileName,
|
||||
fileType: _getAssetType(entity.type),
|
||||
),
|
||||
);
|
||||
|
||||
if (res.statusCode == 201) {
|
||||
var response = await req.send(cancellationToken: cancelToken);
|
||||
|
||||
if (response.statusCode == 201) {
|
||||
singleAssetDoneCb(entity.id, deviceId);
|
||||
} else {
|
||||
var data = await response.stream.bytesToString();
|
||||
var error = jsonDecode(data);
|
||||
|
||||
debugPrint(
|
||||
"Error(${error['statusCode']}) uploading ${entity.id} | $originalFileName | Created on ${entity.createDateTime} | ${error['error']}");
|
||||
|
||||
errorCb(ErrorUploadAsset(
|
||||
asset: entity,
|
||||
id: entity.id,
|
||||
createdAt: entity.createDateTime,
|
||||
fileName: originalFileName,
|
||||
fileType: _getAssetType(entity.type),
|
||||
errorMessage: error['error'],
|
||||
));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} on http.CancelledException {
|
||||
|
@ -108,6 +160,8 @@ class BackupService {
|
|||
}
|
||||
}
|
||||
|
||||
void sendBackupRequest(AssetEntity entity) {}
|
||||
|
||||
String _getAssetType(AssetType assetType) {
|
||||
switch (assetType) {
|
||||
case AssetType.audio:
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart';
|
||||
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
|
@ -9,6 +10,7 @@ import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
|||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||
import 'package:immich_mobile/modules/backup/ui/backup_info_card.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||
|
||||
class BackupControllerPage extends HookConsumerWidget {
|
||||
|
@ -42,7 +44,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
title: const Text(
|
||||
"Server Storage",
|
||||
"Server storage",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||
),
|
||||
subtitle: Padding(
|
||||
|
@ -56,7 +58,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
|
||||
barRadius: const Radius.circular(2),
|
||||
lineHeight: 6.0,
|
||||
lineHeight: 10.0,
|
||||
percent: backupState.serverInfo.diskUsagePercentage / 100.0,
|
||||
backgroundColor: Colors.grey,
|
||||
progressColor: Theme.of(context).primaryColor,
|
||||
|
@ -246,6 +248,141 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
_buildCurrentBackupAssetInfoCard() {
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
Icons.info_outline_rounded,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
"Uploading file info",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||
),
|
||||
if (ref.watch(errorBackupListProvider).isNotEmpty)
|
||||
ActionChip(
|
||||
avatar: Icon(
|
||||
Icons.info,
|
||||
size: 24,
|
||||
color: Colors.red[400],
|
||||
),
|
||||
elevation: 1,
|
||||
visualDensity: VisualDensity.compact,
|
||||
label: Text(
|
||||
"Failed (${ref.watch(errorBackupListProvider).length})",
|
||||
style: TextStyle(
|
||||
color: Colors.red[400],
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
onPressed: () {
|
||||
AutoRouter.of(context).push(const FailedBackupStatusRoute());
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: LinearPercentIndicator(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
|
||||
barRadius: const Radius.circular(2),
|
||||
lineHeight: 10.0,
|
||||
trailing: Text(
|
||||
" ${backupState.progressInPercentage.toStringAsFixed(0)}%",
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
percent: backupState.progressInPercentage / 100.0,
|
||||
backgroundColor: Colors.grey,
|
||||
progressColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Table(
|
||||
border: TableBorder.all(
|
||||
color: Colors.black12,
|
||||
width: 1,
|
||||
),
|
||||
children: [
|
||||
TableRow(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[100],
|
||||
),
|
||||
children: [
|
||||
TableCell(
|
||||
verticalAlignment: TableCellVerticalAlignment.middle,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text(
|
||||
'File name: ${backupState.currentUploadAsset.fileName} [${backupState.currentUploadAsset.fileType.toLowerCase()}]',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TableRow(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
),
|
||||
children: [
|
||||
TableCell(
|
||||
verticalAlignment: TableCellVerticalAlignment.middle,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text(
|
||||
"Created on: ${DateFormat.yMMMMd('en_US').format(
|
||||
DateTime.parse(
|
||||
backupState.currentUploadAsset.createdAt
|
||||
.toString(),
|
||||
),
|
||||
)}",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TableRow(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[100],
|
||||
),
|
||||
children: [
|
||||
TableCell(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text(
|
||||
"ID: ${backupState.currentUploadAsset.id}",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 10.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void startBackup() {
|
||||
ref.watch(errorBackupListProvider.notifier).empty();
|
||||
ref.watch(backupProvider.notifier).startBackupProcess();
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
|
@ -264,7 +401,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||
)),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 32),
|
||||
child: ListView(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
@ -297,23 +434,11 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||
const Divider(),
|
||||
_buildStorageInformation(),
|
||||
const Divider(),
|
||||
_buildCurrentBackupAssetInfoCard(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
"Asset that were being backup: ${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length} [${backupState.progressInPercentage.toStringAsFixed(0)}%]"),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Row(children: [
|
||||
const Text("Backup Progress:"),
|
||||
const Padding(padding: EdgeInsets.symmetric(horizontal: 2)),
|
||||
backupState.backupProgress == BackUpProgressEnum.inProgress
|
||||
? const CircularProgressIndicator.adaptive()
|
||||
: const Text("Done"),
|
||||
]),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24,
|
||||
),
|
||||
child: Container(
|
||||
child:
|
||||
backupState.backupProgress == BackUpProgressEnum.inProgress
|
||||
|
@ -321,25 +446,33 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||
style: ElevatedButton.styleFrom(
|
||||
primary: Colors.red[300],
|
||||
onPrimary: Colors.grey[50],
|
||||
padding: const EdgeInsets.all(14),
|
||||
),
|
||||
onPressed: () {
|
||||
ref.read(backupProvider.notifier).cancelBackup();
|
||||
},
|
||||
child: const Text("Cancel"),
|
||||
child: const Text(
|
||||
"CANCEL",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
)
|
||||
: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Theme.of(context).primaryColor,
|
||||
onPrimary: Colors.grey[50],
|
||||
padding: const EdgeInsets.all(14),
|
||||
),
|
||||
onPressed: shouldBackup ? startBackup : null,
|
||||
child: const Text(
|
||||
"START BACKUP",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
onPressed: shouldBackup
|
||||
? () {
|
||||
ref
|
||||
.read(backupProvider.notifier)
|
||||
.startBackupProcess();
|
||||
}
|
||||
: null,
|
||||
child: const Text("Start Backup"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
139
mobile/lib/modules/backup/views/failed_backup_status_page.dart
Normal file
139
mobile/lib/modules/backup/views/failed_backup_status_page.dart
Normal file
|
@ -0,0 +1,139 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
class FailedBackupStatusPage extends HookConsumerWidget {
|
||||
const FailedBackupStatusPage({Key? key}) : super(key: key);
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final errorBackupList = ref.watch(errorBackupListProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
title: Text(
|
||||
"Failed Backup (${errorBackupList.length})",
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
AutoRouter.of(context).pop(true);
|
||||
},
|
||||
splashRadius: 24,
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_rounded,
|
||||
)),
|
||||
),
|
||||
body: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: errorBackupList.length,
|
||||
itemBuilder: ((context, index) {
|
||||
var errorAsset = errorBackupList.elementAt(index);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 4,
|
||||
),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15), // if you need this
|
||||
side: const BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
elevation: 0,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 100,
|
||||
minHeight: 150,
|
||||
maxWidth: 100,
|
||||
maxHeight: 200,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(15),
|
||||
topLeft: Radius.circular(15),
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: Image(
|
||||
fit: BoxFit.cover,
|
||||
image: AssetEntityImageProvider(
|
||||
errorAsset.asset,
|
||||
isOriginal: false,
|
||||
thumbnailSize: const ThumbnailSize.square(512),
|
||||
thumbnailFormat: ThumbnailFormat.jpeg,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat.yMMMMd('en_US').format(
|
||||
DateTime.parse(
|
||||
errorAsset.createdAt.toString(),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey[700]),
|
||||
),
|
||||
Icon(
|
||||
Icons.error,
|
||||
color: Colors.red.withAlpha(200),
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Text(
|
||||
errorAsset.fileName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
errorAsset.errorMessage,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey[800],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/modules/backup/views/album_preview_page.dart';
|
||||
import 'package:immich_mobile/modules/backup/views/backup_album_selection_page.dart';
|
||||
import 'package:immich_mobile/modules/backup/views/failed_backup_status_page.dart';
|
||||
import 'package:immich_mobile/modules/login/views/change_password_page.dart';
|
||||
import 'package:immich_mobile/modules/login/views/login_page.dart';
|
||||
import 'package:immich_mobile/modules/home/views/home_page.dart';
|
||||
|
@ -65,6 +66,11 @@ part 'router.gr.dart';
|
|||
),
|
||||
AutoRoute(page: BackupAlbumSelectionPage, guards: [AuthGuard]),
|
||||
AutoRoute(page: AlbumPreviewPage, guards: [AuthGuard]),
|
||||
CustomRoute(
|
||||
page: FailedBackupStatusPage,
|
||||
guards: [AuthGuard],
|
||||
transitionsBuilder: TransitionsBuilders.slideBottom,
|
||||
),
|
||||
],
|
||||
)
|
||||
class AppRouter extends _$AppRouter {
|
||||
|
|
|
@ -115,6 +115,14 @@ class _$AppRouter extends RootStackRouter {
|
|||
routeData: routeData,
|
||||
child: AlbumPreviewPage(key: args.key, album: args.album));
|
||||
},
|
||||
FailedBackupStatusRoute.name: (routeData) {
|
||||
return CustomPage<dynamic>(
|
||||
routeData: routeData,
|
||||
child: const FailedBackupStatusPage(),
|
||||
transitionsBuilder: TransitionsBuilders.slideBottom,
|
||||
opaque: true,
|
||||
barrierDismissible: false);
|
||||
},
|
||||
HomeRoute.name: (routeData) {
|
||||
return MaterialPageX<dynamic>(
|
||||
routeData: routeData, child: const HomePage());
|
||||
|
@ -177,7 +185,9 @@ class _$AppRouter extends RootStackRouter {
|
|||
RouteConfig(BackupAlbumSelectionRoute.name,
|
||||
path: '/backup-album-selection-page', guards: [authGuard]),
|
||||
RouteConfig(AlbumPreviewRoute.name,
|
||||
path: '/album-preview-page', guards: [authGuard])
|
||||
path: '/album-preview-page', guards: [authGuard]),
|
||||
RouteConfig(FailedBackupStatusRoute.name,
|
||||
path: '/failed-backup-status-page', guards: [authGuard])
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -437,6 +447,15 @@ class AlbumPreviewRouteArgs {
|
|||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [FailedBackupStatusPage]
|
||||
class FailedBackupStatusRoute extends PageRouteInfo<void> {
|
||||
const FailedBackupStatusRoute()
|
||||
: super(FailedBackupStatusRoute.name, path: '/failed-backup-status-page');
|
||||
|
||||
static const String name = 'FailedBackupStatusRoute';
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [HomePage]
|
||||
class HomeRoute extends PageRouteInfo<void> {
|
||||
|
|
|
@ -79,7 +79,7 @@ class NetworkService {
|
|||
|
||||
return res;
|
||||
} on DioError catch (e) {
|
||||
debugPrint("DioError: ${e.response}");
|
||||
debugPrint("[postRequest] DioError: ${e.response}");
|
||||
return null;
|
||||
} catch (e) {
|
||||
debugPrint("ERROR PostRequest: $e");
|
||||
|
|
|
@ -2,7 +2,7 @@ name: immich_mobile
|
|||
description: Immich - selfhosted backup media file on mobile phone
|
||||
|
||||
publish_to: "none"
|
||||
version: 1.16.1+24
|
||||
version: 1.17.0+25
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
Delete,
|
||||
Logger,
|
||||
HttpCode,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||
import { AssetService } from './asset.service';
|
||||
|
@ -34,6 +35,7 @@ import { Queue } from 'bull';
|
|||
import { IAssetUploadedJob } from '@app/job/index';
|
||||
import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
|
||||
import { assetUploadedProcessorName } from '@app/job/constants/job-name.constant';
|
||||
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('asset')
|
||||
|
@ -66,17 +68,16 @@ export class AssetController {
|
|||
try {
|
||||
const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype);
|
||||
|
||||
if (!savedAsset) {
|
||||
return;
|
||||
if (savedAsset) {
|
||||
await this.assetUploadedQueue.add(
|
||||
assetUploadedProcessorName,
|
||||
{ asset: savedAsset, fileName: file.originalname, fileSize: file.size },
|
||||
{ jobId: savedAsset.id },
|
||||
);
|
||||
}
|
||||
|
||||
await this.assetUploadedQueue.add(
|
||||
assetUploadedProcessorName,
|
||||
{ asset: savedAsset, fileName: file.originalname, fileSize: file.size },
|
||||
{ jobId: savedAsset.id },
|
||||
);
|
||||
} catch (e) {
|
||||
Logger.error(`Error receiving upload file ${e}`);
|
||||
Logger.error(`Error uploading file ${e}`);
|
||||
throw new BadRequestException(`Error uploading file`, `${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,9 +173,9 @@ export class AssetController {
|
|||
@HttpCode(200)
|
||||
async checkDuplicateAsset(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) { deviceAssetId }: { deviceAssetId: string },
|
||||
@Body(ValidationPipe) checkDuplicateAssetDto: CheckDuplicateAssetDto,
|
||||
) {
|
||||
const res = await this.assetService.checkDuplicatedAsset(authUser, deviceAssetId);
|
||||
const res = await this.assetService.checkDuplicatedAsset(authUser, checkDuplicateAssetDto);
|
||||
|
||||
return {
|
||||
isExist: res,
|
||||
|
|
|
@ -18,6 +18,7 @@ import { promisify } from 'util';
|
|||
import { DeleteAssetDto } from './dto/delete-asset.dto';
|
||||
import { SearchAssetDto } from './dto/search-asset.dto';
|
||||
import fs from 'fs/promises';
|
||||
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
||||
|
||||
const fileInfo = promisify(stat);
|
||||
|
||||
|
@ -58,15 +59,11 @@ export class AssetService {
|
|||
asset.mimeType = mimeType;
|
||||
asset.duration = assetInfo.duration || null;
|
||||
|
||||
try {
|
||||
const createdAsset = await this.assetRepository.save(asset);
|
||||
if (!createdAsset) {
|
||||
throw new Error('Asset not created');
|
||||
}
|
||||
return createdAsset;
|
||||
} catch (e) {
|
||||
Logger.error(`Error Create New Asset ${e}`, 'createUserAsset');
|
||||
const createdAsset = await this.assetRepository.save(asset);
|
||||
if (!createdAsset) {
|
||||
throw new Error('Asset not created');
|
||||
}
|
||||
return createdAsset;
|
||||
}
|
||||
|
||||
public async getUserAssetsByDeviceId(authUser: AuthUserDto, deviceId: string) {
|
||||
|
@ -439,10 +436,11 @@ export class AssetService {
|
|||
);
|
||||
}
|
||||
|
||||
async checkDuplicatedAsset(authUser: AuthUserDto, deviceAssetId: string) {
|
||||
async checkDuplicatedAsset(authUser: AuthUserDto, checkDuplicateAssetDto: CheckDuplicateAssetDto) {
|
||||
const res = await this.assetRepository.findOne({
|
||||
where: {
|
||||
deviceAssetId,
|
||||
deviceAssetId: checkDuplicateAssetDto.deviceAssetId,
|
||||
deviceId: checkDuplicateAssetDto.deviceId,
|
||||
userId: authUser.id,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class CheckDuplicateAssetDto {
|
||||
@IsNotEmpty()
|
||||
deviceAssetId!: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
deviceId!: string;
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
export const serverVersion = {
|
||||
major: 1,
|
||||
minor: 16,
|
||||
minor: 17,
|
||||
patch: 0,
|
||||
build: 23,
|
||||
build: 25,
|
||||
};
|
||||
|
|
|
@ -53,7 +53,7 @@ export async function fileUploader(asset: File, accessToken: string) {
|
|||
// Check if asset upload on server before performing upload
|
||||
const res = await fetch(serverEndpoint + '/asset/check', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ deviceAssetId }),
|
||||
body: JSON.stringify({ deviceAssetId, deviceId: 'WEB' }),
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + accessToken,
|
||||
'Content-Type': 'application/json',
|
||||
|
|
Loading…
Reference in a new issue