diff --git a/mobile/lib/interfaces/asset_api.interface.dart b/mobile/lib/interfaces/asset_api.interface.dart index a17e607d83..71ee993a6b 100644 --- a/mobile/lib/interfaces/asset_api.interface.dart +++ b/mobile/lib/interfaces/asset_api.interface.dart @@ -21,4 +21,6 @@ abstract interface class IAssetApiRepository { List list, AssetVisibilityEnum visibility, ); + + Future getAssetMIMEType(String id); } diff --git a/mobile/lib/interfaces/cast_destination_service.interface.dart b/mobile/lib/interfaces/cast_destination_service.interface.dart index 89cc9e6d38..e00023d17f 100644 --- a/mobile/lib/interfaces/cast_destination_service.interface.dart +++ b/mobile/lib/interfaces/cast_destination_service.interface.dart @@ -1,5 +1,5 @@ import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/models/cast_manager_state.dart'; +import 'package:immich_mobile/models/cast/cast_manager_state.dart'; abstract interface class ICastDestinationService { Future initialize(); diff --git a/mobile/lib/interfaces/sessions_api.interface.dart b/mobile/lib/interfaces/sessions_api.interface.dart new file mode 100644 index 0000000000..4b90b77829 --- /dev/null +++ b/mobile/lib/interfaces/sessions_api.interface.dart @@ -0,0 +1,9 @@ +import 'package:immich_mobile/models/sessions/session_create_response.model.dart'; + +abstract interface class ISessionAPIRepository { + Future createSession( + String deviceName, + String deviceOS, { + int? duration, + }); +} diff --git a/mobile/lib/models/cast_manager_state.dart b/mobile/lib/models/cast/cast_manager_state.dart similarity index 100% rename from mobile/lib/models/cast_manager_state.dart rename to mobile/lib/models/cast/cast_manager_state.dart diff --git a/mobile/lib/models/sessions/session_create_response.model.dart b/mobile/lib/models/sessions/session_create_response.model.dart new file mode 100644 index 0000000000..2eeeb51d42 --- /dev/null +++ b/mobile/lib/models/sessions/session_create_response.model.dart @@ -0,0 +1,26 @@ +class SessionCreateResponse { + final String createdAt; + final bool current; + final String deviceOS; + final String deviceType; + final String? expiresAt; + final String id; + final String token; + final String updatedAt; + + SessionCreateResponse({ + required this.createdAt, + required this.current, + required this.deviceOS, + required this.deviceType, + this.expiresAt, + required this.id, + required this.token, + required this.updatedAt, + }); + + @override + String toString() { + return 'SessionCreateResponse[createdAt=$createdAt, current=$current, deviceOS=$deviceOS, deviceType=$deviceType, expiresAt=$expiresAt, id=$id, token=$token, updatedAt=$updatedAt]'; + } +} diff --git a/mobile/lib/pages/common/gallery_viewer.page.dart b/mobile/lib/pages/common/gallery_viewer.page.dart index 54788edc0f..9f90ac6f47 100644 --- a/mobile/lib/pages/common/gallery_viewer.page.dart +++ b/mobile/lib/pages/common/gallery_viewer.page.dart @@ -122,6 +122,8 @@ class GalleryViewerPage extends HookConsumerWidget { useEffect(() { final asset = loadAsset(currentIndex.value); ref.read(castProvider.notifier).loadMedia(asset, false); + + return null; }, [ ref.watch(castProvider).isCasting, ]); diff --git a/mobile/lib/providers/cast.provider.dart b/mobile/lib/providers/cast.provider.dart index e829f84ce8..d09045358c 100644 --- a/mobile/lib/providers/cast.provider.dart +++ b/mobile/lib/providers/cast.provider.dart @@ -1,8 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/models/cast_manager_state.dart'; +import 'package:immich_mobile/models/cast/cast_manager_state.dart'; import 'package:immich_mobile/services/gcast.service.dart'; -import 'package:openapi/api.dart'; final castProvider = StateNotifierProvider( (ref) => CastNotifier(ref.watch(gCastServiceProvider)), diff --git a/mobile/lib/repositories/asset_api.repository.dart b/mobile/lib/repositories/asset_api.repository.dart index 45442c2d61..3974e12ff8 100644 --- a/mobile/lib/repositories/asset_api.repository.dart +++ b/mobile/lib/repositories/asset_api.repository.dart @@ -72,4 +72,11 @@ class AssetApiRepository extends ApiRepository implements IAssetApiRepository { return AssetVisibility.archive; } } + + Future getAssetMIMEType(String assetId) async { + final response = await checkNull(_api.getAssetInfo(assetId)); + + // we need to get the MIME of the thumbnail once that gets added to the API + return response.originalMimeType; + } } diff --git a/mobile/lib/repositories/sessions_api.repository.dart b/mobile/lib/repositories/sessions_api.repository.dart new file mode 100644 index 0000000000..25efed1ee8 --- /dev/null +++ b/mobile/lib/repositories/sessions_api.repository.dart @@ -0,0 +1,45 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/interfaces/sessions_api.interface.dart'; +import 'package:immich_mobile/models/sessions/session_create_response.model.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/repositories/api.repository.dart'; +import 'package:openapi/api.dart'; + +final sessionsAPIRepositoryProvider = Provider( + (ref) => SessionsAPIRepository( + ref.watch(apiServiceProvider).sessionsApi, + ), +); + +class SessionsAPIRepository extends ApiRepository + implements ISessionAPIRepository { + final SessionsApi _api; + + SessionsAPIRepository(this._api); + + @override + Future createSession( + String deviceType, String deviceOS, + {int? duration}) async { + final dto = await checkNull( + _api.createSession( + SessionCreateDto( + deviceType: deviceType, + deviceOS: deviceOS, + duration: duration, + ), + ), + ); + + return SessionCreateResponse( + id: dto.id, + current: dto.current, + deviceType: deviceType, + deviceOS: deviceOS, + expiresAt: dto.expiresAt, + createdAt: dto.createdAt, + updatedAt: dto.updatedAt, + token: dto.token, + ); + } +} diff --git a/mobile/lib/services/gcast.service.dart b/mobile/lib/services/gcast.service.dart index 341b61c9ef..8d1e26dc4d 100644 --- a/mobile/lib/services/gcast.service.dart +++ b/mobile/lib/services/gcast.service.dart @@ -1,30 +1,32 @@ import 'package:cast/device.dart'; import 'package:cast/session.dart'; -import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/interfaces/cast_destination_service.interface.dart'; -import 'package:immich_mobile/models/cast_manager_state.dart'; -import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/models/cast/cast_manager_state.dart'; +import 'package:immich_mobile/models/sessions/session_create_response.model.dart'; +import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/gcast.repository.dart'; -import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/repositories/sessions_api.repository.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/url_helper.dart'; -import 'package:openapi/api.dart' as api; -import 'package:uuid/uuid.dart'; final gCastServiceProvider = Provider( (ref) => GCastService( - ref.watch(gCastRepositoryProvider), ref.watch(apiServiceProvider)), + ref.watch(gCastRepositoryProvider), + ref.watch(sessionsAPIRepositoryProvider), + ref.watch(assetApiRepositoryProvider), + ), ); class GCastService implements ICastDestinationService { final GCastRepository _gCastRepository; - final ApiService _apiService; + final SessionsAPIRepository _sessionsApiService; + final AssetApiRepository _assetApiRepository; - api.SessionCreateResponseDto? sessionKey; + SessionCreateResponse? sessionKey; String? currentAssetId; bool isConnected = false; @@ -39,7 +41,11 @@ class GCastService implements ICastDestinationService { @override void Function(CastState)? onCastState; - GCastService(this._gCastRepository, this._apiService) { + GCastService( + this._gCastRepository, + this._sessionsApiService, + this._assetApiRepository, + ) { _gCastRepository.onCastStatus = _onCastStatusCallback; _gCastRepository.onCastMessage = _onCastMessageCallback; } @@ -103,29 +109,24 @@ class GCastService implements ICastDestinationService { } // create a session key - sessionKey ??= await _apiService.sessionsApi.createSession( - api.SessionCreateDto( - deviceOS: "Google Cast", - deviceType: "Google Cast", - duration: const Duration(minutes: 15).inSeconds, - ), + sessionKey ??= await _sessionsApiService.createSession( + "Cast", + "Google Cast", + duration: const Duration(minutes: 15).inSeconds, ); final unauthenticatedUrl = asset.isVideo ? getPlaybackUrlForRemoteId( asset.remoteId!, ) - : getThumbnailUrlForRemoteId( - asset.remoteId!, - type: api.AssetMediaSize.thumbnail, - ); + : getThumbnailUrlForRemoteId(asset.remoteId!); final authenticatedURL = "$unauthenticatedUrl&sessionKey=${sessionKey?.token}"; // get image mime type - final info = await _apiService.assetsApi.getAssetInfo(asset.remoteId!); - final mimeType = info?.originalMimeType; + final mimeType = + await _assetApiRepository.getAssetMIMEType(asset.remoteId!); if (mimeType == null) { return; diff --git a/mobile/lib/widgets/asset_viewer/cast_dialog.dart b/mobile/lib/widgets/asset_viewer/cast_dialog.dart index d39aa0e5db..b7c422385c 100644 --- a/mobile/lib/widgets/asset_viewer/cast_dialog.dart +++ b/mobile/lib/widgets/asset_viewer/cast_dialog.dart @@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/models/cast_manager_state.dart'; +import 'package:immich_mobile/models/cast/cast_manager_state.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; class CastDialog extends ConsumerWidget {