diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 7b1770ccf6..b5a7e2efe2 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -164,6 +164,7 @@ "home_page_favorite_err_local": "Can not favorite local assets yet, skipping", "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).", "image_viewer_page_state_provider_download_error": "Download Error", + "image_viewer_page_state_provider_share_error": "Share Error", "image_viewer_page_state_provider_download_success": "Download Success", "library_page_albums": "Albums", "library_page_archive": "Archive", diff --git a/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart b/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart index b95a31c13d..6df633f12f 100644 --- a/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart +++ b/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart @@ -57,9 +57,19 @@ class ImageViewerStateNotifier extends StateNotifier { showDialog( context: context, builder: (BuildContext buildContext) { - _shareService - .shareAsset(asset) - .then((_) => Navigator.of(buildContext).pop()); + _shareService.shareAsset(asset).then( + (bool status) { + if (!status) { + ImmichToast.show( + context: context, + msg: 'image_viewer_page_state_provider_share_error'.tr(), + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + } + Navigator.of(buildContext).pop(); + }, + ); return const ShareDialog(); }, barrierDismissible: false, diff --git a/mobile/lib/modules/asset_viewer/services/image_viewer.service.dart b/mobile/lib/modules/asset_viewer/services/image_viewer.service.dart index bd4cc8e310..3a356c8404 100644 --- a/mobile/lib/modules/asset_viewer/services/image_viewer.service.dart +++ b/mobile/lib/modules/asset_viewer/services/image_viewer.service.dart @@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/providers/api.provider.dart'; import 'package:immich_mobile/shared/services/api.service.dart'; +import 'package:logging/logging.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:path_provider/path_provider.dart'; @@ -14,6 +15,7 @@ final imageViewerServiceProvider = class ImageViewerService { final ApiService _apiService; + final Logger _log = Logger("ImageViewerService"); ImageViewerService(this._apiService); @@ -29,6 +31,16 @@ class ImageViewerService { asset.livePhotoVideoId!, ); + if (imageResponse.statusCode != 200 || + motionReponse.statusCode != 200) { + final failedResponse = + imageResponse.statusCode != 200 ? imageResponse : motionReponse; + _log.severe( + "Motion asset download failed with status - ${failedResponse.statusCode} and response - ${failedResponse.body}", + ); + return false; + } + final AssetEntity? entity; final tempDir = await getTemporaryDirectory(); @@ -48,6 +60,13 @@ class ImageViewerService { var res = await _apiService.assetApi .downloadFileWithHttpInfo(asset.remoteId!); + if (res.statusCode != 200) { + _log.severe( + "Asset download failed with status - ${res.statusCode} and response - ${res.body}", + ); + return false; + } + final AssetEntity? entity; if (asset.isImage) { diff --git a/mobile/lib/shared/services/share.service.dart b/mobile/lib/shared/services/share.service.dart index df8f138fb5..ab3a096aee 100644 --- a/mobile/lib/shared/services/share.service.dart +++ b/mobile/lib/shared/services/share.service.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/providers/api.provider.dart'; +import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import 'api.service.dart'; @@ -13,32 +14,60 @@ final shareServiceProvider = class ShareService { final ApiService _apiService; + final Logger _log = Logger("ShareService"); ShareService(this._apiService); - Future shareAsset(Asset asset) async { - await shareAssets([asset]); + Future shareAsset(Asset asset) async { + return await shareAssets([asset]); } - Future shareAssets(List assets) async { - final downloadedXFiles = assets.map>((asset) async { - if (asset.isRemote) { - final tempDir = await getTemporaryDirectory(); - final fileName = asset.fileName; - final tempFile = await File('${tempDir.path}/$fileName').create(); - final res = await _apiService.assetApi - .downloadFileWithHttpInfo(asset.remoteId!); - tempFile.writeAsBytesSync(res.bodyBytes); - return XFile(tempFile.path); - } else { - File? f = await asset.local!.file; - return XFile(f!.path); - } - }); + Future shareAssets(List assets) async { + try { + final downloadedXFiles = []; - Share.shareXFiles( - await Future.wait(downloadedXFiles), - sharePositionOrigin: Rect.zero, - ); + for (var asset in assets) { + if (asset.isRemote) { + final tempDir = await getTemporaryDirectory(); + final fileName = asset.fileName; + final tempFile = await File('${tempDir.path}/$fileName').create(); + final res = await _apiService.assetApi + .downloadFileWithHttpInfo(asset.remoteId!); + + if (res.statusCode != 200) { + _log.severe( + "Asset download failed with status - ${res.statusCode} and response - ${res.body}", + ); + continue; + } + + tempFile.writeAsBytesSync(res.bodyBytes); + downloadedXFiles.add(XFile(tempFile.path)); + } else { + File? f = await asset.local!.file; + downloadedXFiles.add(XFile(f!.path)); + } + } + + if (downloadedXFiles.isEmpty) { + _log.warning("No asset can be retrieved for share"); + return false; + } + + if (downloadedXFiles.length != assets.length) { + _log.warning( + "Partial share - Requested: ${assets.length}, Sharing: ${downloadedXFiles.length}", + ); + } + + Share.shareXFiles( + downloadedXFiles, + sharePositionOrigin: Rect.zero, + ); + return true; + } catch (error) { + _log.severe("Share failed with error $error"); + } + return false; } } diff --git a/mobile/lib/utils/selection_handlers.dart b/mobile/lib/utils/selection_handlers.dart index 08de7961b6..511dcf81ea 100644 --- a/mobile/lib/utils/selection_handlers.dart +++ b/mobile/lib/utils/selection_handlers.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -15,10 +16,19 @@ void handleShareAssets( showDialog( context: context, builder: (BuildContext buildContext) { - ref - .watch(shareServiceProvider) - .shareAssets(selection.toList()) - .then((_) => Navigator.of(buildContext).pop()); + ref.watch(shareServiceProvider).shareAssets(selection.toList()).then( + (bool status) { + if (!status) { + ImmichToast.show( + context: context, + msg: 'image_viewer_page_state_provider_share_error'.tr(), + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + } + Navigator.of(buildContext).pop(); + }, + ); return const ShareDialog(); }, barrierDismissible: false,