You've already forked immich
							
							
				mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 00:18:28 +02:00 
			
		
		
		
	Enable swiping between assets (#381)
Enable swiping between assets (#381) Co-authored-by: Alex <alex.tran1502@gmail.com> Co-authored-by: Malte Kiefer <59220985+MalteKiefer@users.noreply.github.com> Co-authored-by: Matthias Rupp <matthias.rupp@posteo.de>
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							e8d1f89a47
						
					
				
				
					commit
					8c184dc4d4
				
			
							
								
								
									
										2
									
								
								mobile/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								mobile/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -24,7 +24,7 @@ | ||||
|  | ||||
| # Flutter/Dart/Pub related | ||||
| **/doc/api/ | ||||
| **/ios/Flutter/.last_build_id | ||||
| **/ios/ | ||||
| .dart_tool/ | ||||
| .flutter-plugins | ||||
| .flutter-plugins-dependencies | ||||
|   | ||||
| @@ -17,7 +17,6 @@ import 'package:immich_mobile/shared/providers/server_info.provider.dart'; | ||||
| import 'package:immich_mobile/shared/providers/websocket.provider.dart'; | ||||
| import 'package:immich_mobile/shared/views/immich_loading_overlay.dart'; | ||||
| import 'package:immich_mobile/shared/views/version_announcement_overlay.dart'; | ||||
|  | ||||
| import 'constants/hive_box.dart'; | ||||
|  | ||||
| void main() async { | ||||
|   | ||||
| @@ -20,7 +20,9 @@ class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> { | ||||
|   } | ||||
|  | ||||
|   Future<AlbumResponseDto?> createAlbum( | ||||
|       String albumTitle, Set<AssetResponseDto> assets) async { | ||||
|     String albumTitle, | ||||
|     Set<AssetResponseDto> assets, | ||||
|   ) async { | ||||
|     AlbumResponseDto? album = | ||||
|         await _albumService.createAlbum(albumTitle, assets, []); | ||||
|  | ||||
|   | ||||
| @@ -12,8 +12,13 @@ import 'package:openapi/api.dart'; | ||||
|  | ||||
| class AlbumViewerThumbnail extends HookConsumerWidget { | ||||
|   final AssetResponseDto asset; | ||||
|   final List<AssetResponseDto> assetList; | ||||
|  | ||||
|   const AlbumViewerThumbnail({Key? key, required this.asset}) : super(key: key); | ||||
|   const AlbumViewerThumbnail({ | ||||
|     Key? key, | ||||
|     required this.asset, | ||||
|     required this.assetList, | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
| @@ -28,25 +33,13 @@ class AlbumViewerThumbnail extends HookConsumerWidget { | ||||
|         ref.watch(assetSelectionProvider).isMultiselectEnable; | ||||
|  | ||||
|     _viewAsset() { | ||||
|       if (asset.type == AssetTypeEnum.IMAGE) { | ||||
|         AutoRouter.of(context).push( | ||||
|           ImageViewerRoute( | ||||
|             imageUrl: | ||||
|                 '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false', | ||||
|             heroTag: asset.id, | ||||
|             thumbnailUrl: thumbnailRequestUrl, | ||||
|             asset: asset, | ||||
|           ), | ||||
|         ); | ||||
|       } else { | ||||
|         AutoRouter.of(context).push( | ||||
|           VideoViewerRoute( | ||||
|             videoUrl: | ||||
|                 '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}', | ||||
|             asset: asset, | ||||
|           ), | ||||
|         ); | ||||
|       } | ||||
|       AutoRouter.of(context).push( | ||||
|         GalleryViewerRoute( | ||||
|           asset: asset, | ||||
|           assetList: assetList, | ||||
|           thumbnailRequestUrl: thumbnailRequestUrl, | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     BoxBorder drawBorderColor() { | ||||
|   | ||||
| @@ -29,9 +29,7 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     FocusNode titleFocusNode = useFocusNode(); | ||||
|     ScrollController scrollController = useScrollController(); | ||||
|  | ||||
|     AsyncValue<AlbumResponseDto?> albumInfo = | ||||
|         ref.watch(sharedAlbumDetailProvider(albumId)); | ||||
|     var albumInfo = ref.watch(sharedAlbumDetailProvider(albumId)); | ||||
|  | ||||
|     final userId = ref.watch(authenticationProvider).userId; | ||||
|  | ||||
| @@ -200,7 +198,10 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|             ), | ||||
|             delegate: SliverChildBuilderDelegate( | ||||
|               (BuildContext context, int index) { | ||||
|                 return AlbumViewerThumbnail(asset: albumInfo.assets[index]); | ||||
|                 return AlbumViewerThumbnail( | ||||
|                   asset: albumInfo.assets[index], | ||||
|                   assetList: albumInfo.assets, | ||||
|                 ); | ||||
|               }, | ||||
|               childCount: albumInfo.assets.length, | ||||
|             ), | ||||
|   | ||||
| @@ -15,7 +15,6 @@ class _RemotePhotoViewState extends State<RemotePhotoView> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     bool allowMoving = _status == _RemoteImageStatus.full; | ||||
|  | ||||
|     return PhotoView( | ||||
|       imageProvider: _imageProvider, | ||||
|       minScale: PhotoViewComputedScale.contained, | ||||
| @@ -32,8 +31,9 @@ class _RemotePhotoViewState extends State<RemotePhotoView> { | ||||
|     PhotoViewControllerValue controllerValue, | ||||
|   ) { | ||||
|     // Disable swipe events when zoomed in | ||||
|     if (_zoomedIn) return; | ||||
|  | ||||
|     if (_zoomedIn) { | ||||
|       return; | ||||
|     } | ||||
|     if (controllerValue.position.dy > swipeThreshold) { | ||||
|       widget.onSwipeDown(); | ||||
|     } else if (controllerValue.position.dy < -swipeThreshold) { | ||||
| @@ -42,7 +42,14 @@ class _RemotePhotoViewState extends State<RemotePhotoView> { | ||||
|   } | ||||
|  | ||||
|   void _scaleStateChanged(PhotoViewScaleState state) { | ||||
|     _zoomedIn = state == PhotoViewScaleState.zoomedIn; | ||||
|     // _onScaleListener; | ||||
|     _zoomedIn = state != PhotoViewScaleState.initial; | ||||
|     if (_zoomedIn) { | ||||
|       widget.isZoomedListener.value = true; | ||||
|     } else { | ||||
|       widget.isZoomedListener.value = false; | ||||
|     } | ||||
|     widget.isZoomedFunction(); | ||||
|   } | ||||
|  | ||||
|   CachedNetworkImageProvider _authorizedImageProvider(String url) { | ||||
| @@ -107,6 +114,8 @@ class RemotePhotoView extends StatefulWidget { | ||||
|     required this.thumbnailUrl, | ||||
|     required this.imageUrl, | ||||
|     required this.authToken, | ||||
|     required this.isZoomedFunction, | ||||
|     required this.isZoomedListener, | ||||
|     required this.onSwipeDown, | ||||
|     required this.onSwipeUp, | ||||
|   }) : super(key: key); | ||||
| @@ -117,6 +126,9 @@ class RemotePhotoView extends StatefulWidget { | ||||
|  | ||||
|   final void Function() onSwipeDown; | ||||
|   final void Function() onSwipeUp; | ||||
|   final void Function() isZoomedFunction; | ||||
|  | ||||
|   final ValueNotifier<bool> isZoomedListener; | ||||
|  | ||||
|   @override | ||||
|   State<StatefulWidget> createState() { | ||||
|   | ||||
							
								
								
									
										134
									
								
								mobile/lib/modules/asset_viewer/views/gallery_viewer.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								mobile/lib/modules/asset_viewer/views/gallery_viewer.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:flutter_swipe_detector/flutter_swipe_detector.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/asset_viewer/providers/image_viewer_page_state.provider.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/views/image_viewer_page.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart'; | ||||
| import 'package:immich_mobile/modules/home/services/asset.service.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| // ignore: must_be_immutable | ||||
| class GalleryViewerPage extends HookConsumerWidget { | ||||
|   late List<AssetResponseDto> assetList; | ||||
|   final AssetResponseDto asset; | ||||
|   final String thumbnailRequestUrl; | ||||
|  | ||||
|   GalleryViewerPage({ | ||||
|     Key? key, | ||||
|     required this.assetList, | ||||
|     required this.asset, | ||||
|     required this.thumbnailRequestUrl, | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   AssetResponseDto? assetDetail; | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final Box<dynamic> box = Hive.box(userInfoBox); | ||||
|  | ||||
|     int indexOfAsset = assetList.indexOf(asset); | ||||
|  | ||||
|     @override | ||||
|     void initState(int index) { | ||||
|       indexOfAsset = index; | ||||
|     } | ||||
|  | ||||
|     PageController controller = | ||||
|         PageController(initialPage: assetList.indexOf(asset)); | ||||
|  | ||||
|     getAssetExif() async { | ||||
|       assetDetail = await ref | ||||
|           .watch(assetServiceProvider) | ||||
|           .getAssetById(assetList[indexOfAsset].id); | ||||
|     } | ||||
|  | ||||
|     void showInfo() { | ||||
|       showModalBottomSheet( | ||||
|         backgroundColor: Colors.black, | ||||
|         barrierColor: Colors.transparent, | ||||
|         isScrollControlled: false, | ||||
|         context: context, | ||||
|         builder: (context) { | ||||
|           return ExifBottomSheet(assetDetail: assetDetail!); | ||||
|         }, | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     final isZoomed = useState<bool>(false); | ||||
|     ValueNotifier<bool> isZoomedListener = ValueNotifier<bool>(false); | ||||
|  | ||||
|     //make isZoomed listener call instead | ||||
|     void isZoomedMethod() { | ||||
|       if (isZoomedListener.value) { | ||||
|         isZoomed.value = true; | ||||
|       } else { | ||||
|         isZoomed.value = false; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return Scaffold( | ||||
|       backgroundColor: Colors.black, | ||||
|       appBar: TopControlAppBar( | ||||
|         asset: assetList[indexOfAsset], | ||||
|         onMoreInfoPressed: () { | ||||
|           showInfo(); | ||||
|         }, | ||||
|         onDownloadPressed: () { | ||||
|           ref | ||||
|               .watch(imageViewerStateProvider.notifier) | ||||
|               .downloadAsset(assetList[indexOfAsset], context); | ||||
|         }, | ||||
|       ), | ||||
|       body: SafeArea( | ||||
|         child: PageView.builder( | ||||
|           controller: controller, | ||||
|           pageSnapping: true, | ||||
|           physics: isZoomed.value | ||||
|               ? const NeverScrollableScrollPhysics() | ||||
|               : const BouncingScrollPhysics(), | ||||
|           itemCount: assetList.length, | ||||
|           scrollDirection: Axis.horizontal, | ||||
|           itemBuilder: (context, index) { | ||||
|             initState(index); | ||||
|             getAssetExif(); | ||||
|             if (assetList[index].type == AssetTypeEnum.IMAGE) { | ||||
|               return ImageViewerPage( | ||||
|                 thumbnailUrl: | ||||
|                     '${box.get(serverEndpointKey)}/asset/thumbnail/${assetList[index].id}', | ||||
|                 imageUrl: | ||||
|                     '${box.get(serverEndpointKey)}/asset/file?aid=${assetList[index].deviceAssetId}&did=${assetList[index].deviceId}&isThumb=false', | ||||
|                 authToken: 'Bearer ${box.get(accessTokenKey)}', | ||||
|                 isZoomedFunction: isZoomedMethod, | ||||
|                 isZoomedListener: isZoomedListener, | ||||
|                 asset: assetList[index], | ||||
|                 heroTag: assetList[index].id, | ||||
|               ); | ||||
|             } else { | ||||
|               return SwipeDetector( | ||||
|                 onSwipeDown: (_) { | ||||
|                   AutoRouter.of(context).pop(); | ||||
|                 }, | ||||
|                 onSwipeUp: (_) { | ||||
|                   showInfo(); | ||||
|                 }, | ||||
|                 child: Hero( | ||||
|                   tag: assetList[index].id, | ||||
|                   child: VideoViewerPage( | ||||
|                     asset: assetList[index], | ||||
|                     videoUrl: | ||||
|                         '${box.get(serverEndpointKey)}/asset/file?aid=${assetList[index].deviceAssetId}&did=${assetList[index].deviceId}', | ||||
|                   ), | ||||
|                 ), | ||||
|               ); | ||||
|             } | ||||
|           }, | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -1,15 +1,12 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.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/asset_viewer/models/image_viewer_page_state.model.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/ui/download_loading_indicator.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/ui/remote_photo_view.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart'; | ||||
| import 'package:immich_mobile/modules/home/services/asset.service.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| @@ -19,8 +16,9 @@ class ImageViewerPage extends HookConsumerWidget { | ||||
|   final String heroTag; | ||||
|   final String thumbnailUrl; | ||||
|   final AssetResponseDto asset; | ||||
|  | ||||
|   AssetResponseDto? assetDetail; | ||||
|   final String authToken; | ||||
|   final ValueNotifier<bool> isZoomedListener; | ||||
|   final void Function() isZoomedFunction; | ||||
|  | ||||
|   ImageViewerPage({ | ||||
|     Key? key, | ||||
| @@ -28,31 +26,22 @@ class ImageViewerPage extends HookConsumerWidget { | ||||
|     required this.heroTag, | ||||
|     required this.thumbnailUrl, | ||||
|     required this.asset, | ||||
|     required this.authToken, | ||||
|     required this.isZoomedFunction, | ||||
|     required this.isZoomedListener, | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   AssetResponseDto? assetDetail; | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final downloadAssetStatus = | ||||
|         ref.watch(imageViewerStateProvider).downloadAssetStatus; | ||||
|     var box = Hive.box(userInfoBox); | ||||
|  | ||||
|     getAssetExif() async { | ||||
|       assetDetail = | ||||
|           await ref.watch(assetServiceProvider).getAssetById(asset.id); | ||||
|     } | ||||
|  | ||||
|     showInfo() { | ||||
|       showModalBottomSheet( | ||||
|         backgroundColor: Colors.black, | ||||
|         barrierColor: Colors.transparent, | ||||
|         isScrollControlled: false, | ||||
|         context: context, | ||||
|         builder: (context) { | ||||
|           return ExifBottomSheet(assetDetail: assetDetail!); | ||||
|         }, | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     useEffect( | ||||
|       () { | ||||
|         getAssetExif(); | ||||
| @@ -61,39 +50,39 @@ class ImageViewerPage extends HookConsumerWidget { | ||||
|       [], | ||||
|     ); | ||||
|  | ||||
|     return Scaffold( | ||||
|       backgroundColor: Colors.black, | ||||
|       appBar: TopControlAppBar( | ||||
|         asset: asset, | ||||
|         onMoreInfoPressed: showInfo, | ||||
|         onDownloadPressed: () { | ||||
|           ref | ||||
|               .watch(imageViewerStateProvider.notifier) | ||||
|               .downloadAsset(asset, context); | ||||
|     showInfo() { | ||||
|       showModalBottomSheet( | ||||
|         backgroundColor: Colors.black, | ||||
|         barrierColor: Colors.transparent, | ||||
|         isScrollControlled: false, | ||||
|         context: context, | ||||
|         builder: (context) { | ||||
|           return ExifBottomSheet(assetDetail: assetDetail ?? asset); | ||||
|         }, | ||||
|       ), | ||||
|       body: SafeArea( | ||||
|         child: Stack( | ||||
|           children: [ | ||||
|             Center( | ||||
|               child: Hero( | ||||
|                 tag: heroTag, | ||||
|                 child: RemotePhotoView( | ||||
|                   thumbnailUrl: thumbnailUrl, | ||||
|                   imageUrl: imageUrl, | ||||
|                   authToken: "Bearer ${box.get(accessTokenKey)}", | ||||
|                   onSwipeDown: () => AutoRouter.of(context).pop(), | ||||
|                   onSwipeUp: () => showInfo(), | ||||
|                 ), | ||||
|               ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return Stack( | ||||
|       children: [ | ||||
|         Center( | ||||
|           child: Hero( | ||||
|             tag: heroTag, | ||||
|             child: RemotePhotoView( | ||||
|               thumbnailUrl: thumbnailUrl, | ||||
|               imageUrl: imageUrl, | ||||
|               authToken: authToken, | ||||
|               isZoomedFunction: isZoomedFunction, | ||||
|               isZoomedListener: isZoomedListener, | ||||
|               onSwipeDown: () => AutoRouter.of(context).pop(), | ||||
|               onSwipeUp: () => showInfo(), | ||||
|             ), | ||||
|             if (downloadAssetStatus == DownloadAssetStatus.loading) | ||||
|               const Center( | ||||
|                 child: DownloadLoadingIndicator(), | ||||
|               ), | ||||
|           ], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|         if (downloadAssetStatus == DownloadAssetStatus.loading) | ||||
|           const Center( | ||||
|             child: DownloadLoadingIndicator(), | ||||
|           ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,4 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; | ||||
| import 'package:hive/hive.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/constants/hive_box.dart'; | ||||
| @@ -9,9 +6,6 @@ import 'package:chewie/chewie.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/ui/download_loading_indicator.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart'; | ||||
| import 'package:immich_mobile/modules/home/services/asset.service.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:video_player/video_player.dart'; | ||||
|  | ||||
| @@ -31,66 +25,17 @@ class VideoViewerPage extends HookConsumerWidget { | ||||
|  | ||||
|     String jwtToken = Hive.box(userInfoBox).get(accessTokenKey); | ||||
|  | ||||
|     void showInfo() { | ||||
|       showModalBottomSheet( | ||||
|         backgroundColor: Colors.black, | ||||
|         barrierColor: Colors.transparent, | ||||
|         isScrollControlled: false, | ||||
|         context: context, | ||||
|         builder: (context) { | ||||
|           return ExifBottomSheet(assetDetail: assetDetail!); | ||||
|         }, | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     getAssetExif() async { | ||||
|       assetDetail = | ||||
|           await ref.watch(assetServiceProvider).getAssetById(asset.id); | ||||
|     } | ||||
|  | ||||
|     useEffect( | ||||
|       () { | ||||
|         getAssetExif(); | ||||
|         return null; | ||||
|       }, | ||||
|       [], | ||||
|     ); | ||||
|  | ||||
|     return Scaffold( | ||||
|       backgroundColor: Colors.black, | ||||
|       appBar: TopControlAppBar( | ||||
|         asset: asset, | ||||
|         onMoreInfoPressed: () { | ||||
|           showInfo(); | ||||
|         }, | ||||
|         onDownloadPressed: () { | ||||
|           ref | ||||
|               .watch(imageViewerStateProvider.notifier) | ||||
|               .downloadAsset(asset, context); | ||||
|         }, | ||||
|       ), | ||||
|       body: SwipeDetector( | ||||
|         onSwipeDown: (_) { | ||||
|           AutoRouter.of(context).pop(); | ||||
|         }, | ||||
|         onSwipeUp: (_) { | ||||
|           showInfo(); | ||||
|         }, | ||||
|         child: SafeArea( | ||||
|           child: Stack( | ||||
|             children: [ | ||||
|               VideoThumbnailPlayer( | ||||
|                 url: videoUrl, | ||||
|                 jwtToken: jwtToken, | ||||
|               ), | ||||
|               if (downloadAssetStatus == DownloadAssetStatus.loading) | ||||
|                 const Center( | ||||
|                   child: DownloadLoadingIndicator(), | ||||
|                 ), | ||||
|             ], | ||||
|           ), | ||||
|     return Stack( | ||||
|       children: [ | ||||
|         VideoThumbnailPlayer( | ||||
|           url: videoUrl, | ||||
|           jwtToken: jwtToken, | ||||
|         ), | ||||
|       ), | ||||
|         if (downloadAssetStatus == DownloadAssetStatus.loading) | ||||
|           const Center( | ||||
|             child: DownloadLoadingIndicator(), | ||||
|           ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -134,10 +79,13 @@ class _VideoThumbnailPlayerState extends State<VideoThumbnailPlayer> { | ||||
|   _createChewieController() { | ||||
|     chewieController = ChewieController( | ||||
|       showOptions: true, | ||||
|       showControlsOnInitialize: false, | ||||
|       showControlsOnInitialize: true, | ||||
|       videoPlayerController: videoPlayerController, | ||||
|       autoPlay: true, | ||||
|       autoInitialize: false, | ||||
|       autoInitialize: true, | ||||
|       allowFullScreen: true, | ||||
|       showControls: true, | ||||
|       hideControlsTimer: const Duration(seconds: 5), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @@ -157,11 +105,13 @@ class _VideoThumbnailPlayerState extends State<VideoThumbnailPlayer> { | ||||
|               controller: chewieController!, | ||||
|             ), | ||||
|           ) | ||||
|         : const SizedBox( | ||||
|             width: 75, | ||||
|             height: 75, | ||||
|             child: CircularProgressIndicator.adaptive( | ||||
|               strokeWidth: 2, | ||||
|         : const Center( | ||||
|             child: SizedBox( | ||||
|               width: 75, | ||||
|               height: 75, | ||||
|               child: CircularProgressIndicator.adaptive( | ||||
|                 strokeWidth: 2, | ||||
|               ), | ||||
|             ), | ||||
|           ); | ||||
|   } | ||||
|   | ||||
| @@ -162,6 +162,10 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|         onlyAll: true, | ||||
|         type: RequestType.common, | ||||
|       ); | ||||
|  | ||||
|       if (list.isEmpty) { | ||||
|         return; | ||||
|       } | ||||
|       AssetPathEntity albumHasAllAssets = list.first; | ||||
|  | ||||
|       backupAlbumInfoBox.put( | ||||
|   | ||||
| @@ -3,10 +3,18 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/home/ui/thumbnail_image.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| // ignore: must_be_immutable | ||||
| class ImageGrid extends ConsumerWidget { | ||||
|   final List<AssetResponseDto> assetGroup; | ||||
|   final List<AssetResponseDto> sortedAssetGroup; | ||||
|  | ||||
|   const ImageGrid({Key? key, required this.assetGroup}) : super(key: key); | ||||
|   ImageGrid({ | ||||
|     Key? key, | ||||
|     required this.assetGroup, | ||||
|     required this.sortedAssetGroup, | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   List<AssetResponseDto> imageSortedList = []; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
| @@ -19,12 +27,14 @@ class ImageGrid extends ConsumerWidget { | ||||
|       delegate: SliverChildBuilderDelegate( | ||||
|         (BuildContext context, int index) { | ||||
|           var assetType = assetGroup[index].type; | ||||
|  | ||||
|           return GestureDetector( | ||||
|             onTap: () {}, | ||||
|             child: Stack( | ||||
|               children: [ | ||||
|                 ThumbnailImage(asset: assetGroup[index]), | ||||
|                 ThumbnailImage( | ||||
|                   asset: assetGroup[index], | ||||
|                   assetList: sortedAssetGroup, | ||||
|                 ), | ||||
|                 if (assetType != AssetTypeEnum.IMAGE) | ||||
|                   Positioned( | ||||
|                     top: 5, | ||||
|   | ||||
| @@ -13,8 +13,10 @@ import 'package:openapi/api.dart'; | ||||
|  | ||||
| class ThumbnailImage extends HookConsumerWidget { | ||||
|   final AssetResponseDto asset; | ||||
|   final List<AssetResponseDto> assetList; | ||||
|  | ||||
|   const ThumbnailImage({Key? key, required this.asset}) : super(key: key); | ||||
|   const ThumbnailImage({Key? key, required this.asset, required this.assetList}) | ||||
|       : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
| @@ -60,29 +62,17 @@ class ThumbnailImage extends HookConsumerWidget { | ||||
|               .watch(homePageStateProvider.notifier) | ||||
|               .addSingleSelectedItem(asset); | ||||
|         } else { | ||||
|           if (asset.type == AssetTypeEnum.IMAGE) { | ||||
|             AutoRouter.of(context).push( | ||||
|               ImageViewerRoute( | ||||
|                 imageUrl: | ||||
|                     '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false', | ||||
|                 heroTag: asset.id, | ||||
|                 thumbnailUrl: thumbnailRequestUrl, | ||||
|                 asset: asset, | ||||
|               ), | ||||
|             ); | ||||
|           } else { | ||||
|             AutoRouter.of(context).push( | ||||
|               VideoViewerRoute( | ||||
|                 videoUrl: | ||||
|                     '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}', | ||||
|                 asset: asset, | ||||
|               ), | ||||
|             ); | ||||
|           } | ||||
|           AutoRouter.of(context).push( | ||||
|             GalleryViewerRoute( | ||||
|               assetList: assetList, | ||||
|               thumbnailRequestUrl: thumbnailRequestUrl, | ||||
|               asset: asset, | ||||
|             ), | ||||
|           ); | ||||
|         } | ||||
|       }, | ||||
|       onLongPress: () { | ||||
|         // Enable multi selecte function | ||||
|         // Enable multi select function | ||||
|         ref.watch(homePageStateProvider.notifier).enableMultiSelect({asset}); | ||||
|         HapticFeedback.heavyImpact(); | ||||
|       }, | ||||
|   | ||||
| @@ -10,9 +10,11 @@ import 'package:immich_mobile/modules/home/ui/image_grid.dart'; | ||||
| import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart'; | ||||
| import 'package:immich_mobile/modules/home/ui/monthly_title_text.dart'; | ||||
| import 'package:immich_mobile/modules/home/ui/profile_drawer.dart'; | ||||
|  | ||||
| import 'package:immich_mobile/shared/providers/asset.provider.dart'; | ||||
| import 'package:immich_mobile/shared/providers/server_info.provider.dart'; | ||||
| import 'package:immich_mobile/shared/providers/websocket.provider.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class HomePage extends HookConsumerWidget { | ||||
|   const HomePage({Key? key}) : super(key: key); | ||||
| @@ -25,6 +27,13 @@ class HomePage extends HookConsumerWidget { | ||||
|     var isMultiSelectEnable = | ||||
|         ref.watch(homePageStateProvider).isMultiSelectEnable; | ||||
|     var homePageState = ref.watch(homePageStateProvider); | ||||
|     List<AssetResponseDto> sortedAssetList = []; | ||||
|     // set sorted List | ||||
|     for (var group in assetGroupByDateTime.values) { | ||||
|       for (var value in group) { | ||||
|         sortedAssetList.add(value); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     useEffect( | ||||
|       () { | ||||
| @@ -73,7 +82,10 @@ class HomePage extends HookConsumerWidget { | ||||
|           ); | ||||
|  | ||||
|           imageGridGroup.add( | ||||
|             ImageGrid(assetGroup: immichAssetList), | ||||
|             ImageGrid( | ||||
|               assetGroup: immichAssetList, | ||||
|               sortedAssetGroup: sortedAssetList, | ||||
|             ), | ||||
|           ); | ||||
|  | ||||
|           lastMonth = currentMonth; | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import 'package:immich_mobile/modules/home/ui/monthly_title_text.dart'; | ||||
| import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart'; | ||||
| import 'package:immich_mobile/modules/search/providers/search_result_page.provider.dart'; | ||||
| import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class SearchResultPage extends HookConsumerWidget { | ||||
|   const SearchResultPage({Key? key, required this.searchTerm}) | ||||
| @@ -27,7 +28,9 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|  | ||||
|     final List<Widget> imageGridGroup = []; | ||||
|  | ||||
|     late FocusNode searchFocusNode; | ||||
|     FocusNode? searchFocusNode; | ||||
|  | ||||
|     List<AssetResponseDto> sortedAssetList = []; | ||||
|  | ||||
|     useEffect( | ||||
|       () { | ||||
| @@ -37,14 +40,14 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|           Duration.zero, | ||||
|           () => ref.read(searchResultPageProvider.notifier).search(searchTerm), | ||||
|         ); | ||||
|         return () => searchFocusNode.dispose(); | ||||
|         return () => searchFocusNode?.dispose(); | ||||
|       }, | ||||
|       [], | ||||
|     ); | ||||
|  | ||||
|     _onSearchSubmitted(String newSearchTerm) { | ||||
|       debugPrint("Re-Search with $newSearchTerm"); | ||||
|       searchFocusNode.unfocus(); | ||||
|       searchFocusNode?.unfocus(); | ||||
|       isNewSearch.value = false; | ||||
|       currentSearchTerm.value = newSearchTerm; | ||||
|       ref.watch(searchResultPageProvider.notifier).search(newSearchTerm); | ||||
| @@ -58,7 +61,7 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|         onTap: () { | ||||
|           searchTermController.clear(); | ||||
|           ref.watch(searchPageStateProvider.notifier).setSearchTerm(""); | ||||
|           searchFocusNode.requestFocus(); | ||||
|           searchFocusNode?.requestFocus(); | ||||
|         }, | ||||
|         textInputAction: TextInputAction.search, | ||||
|         onSubmitted: (searchTerm) { | ||||
| @@ -131,7 +134,12 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|       if (searchResultPageState.isSuccess) { | ||||
|         if (searchResultPageState.searchResult.isNotEmpty) { | ||||
|           int? lastMonth; | ||||
|  | ||||
|           // set sorted List | ||||
|           for (var group in assetGroupByDateTime.values) { | ||||
|             for (var value in group) { | ||||
|               sortedAssetList.add(value); | ||||
|             } | ||||
|           } | ||||
|           assetGroupByDateTime.forEach((dateGroup, immichAssetList) { | ||||
|             DateTime parseDateGroup = DateTime.parse(dateGroup); | ||||
|             int currentMonth = parseDateGroup.month; | ||||
| @@ -154,7 +162,10 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|             ); | ||||
|  | ||||
|             imageGridGroup.add( | ||||
|               ImageGrid(assetGroup: immichAssetList), | ||||
|               ImageGrid( | ||||
|                 assetGroup: immichAssetList, | ||||
|                 sortedAssetGroup: sortedAssetList, | ||||
|               ), | ||||
|             ); | ||||
|  | ||||
|             lastMonth = currentMonth; | ||||
| @@ -193,7 +204,7 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|         title: GestureDetector( | ||||
|           onTap: () { | ||||
|             isNewSearch.value = true; | ||||
|             searchFocusNode.requestFocus(); | ||||
|             searchFocusNode?.requestFocus(); | ||||
|           }, | ||||
|           child: isNewSearch.value ? _buildTextField() : _buildChip(), | ||||
|         ), | ||||
| @@ -201,7 +212,10 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|       ), | ||||
|       body: GestureDetector( | ||||
|         onTap: () { | ||||
|           searchFocusNode.unfocus(); | ||||
|           if (searchFocusNode != null) { | ||||
|             searchFocusNode?.unfocus(); | ||||
|           } | ||||
|  | ||||
|           ref.watch(searchPageStateProvider.notifier).disableSearch(); | ||||
|         }, | ||||
|         child: Stack( | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/album/views/library_page.dart'; | ||||
| import 'package:immich_mobile/modules/asset_viewer/views/gallery_viewer.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'; | ||||
| @@ -47,6 +48,7 @@ part 'router.gr.dart'; | ||||
|       ], | ||||
|       transitionsBuilder: TransitionsBuilders.fadeIn, | ||||
|     ), | ||||
|     AutoRoute(page: GalleryViewerPage, guards: [AuthGuard]), | ||||
|     AutoRoute(page: ImageViewerPage, guards: [AuthGuard]), | ||||
|     AutoRoute(page: VideoViewerPage, guards: [AuthGuard]), | ||||
|     AutoRoute(page: BackupControllerPage, guards: [AuthGuard]), | ||||
| @@ -78,6 +80,7 @@ part 'router.gr.dart'; | ||||
|   ], | ||||
| ) | ||||
| class AppRouter extends _$AppRouter { | ||||
|   // ignore: unused_field | ||||
|   final ApiService _apiService; | ||||
|  | ||||
|   AppRouter(this._apiService) : super(authGuard: AuthGuard(_apiService)); | ||||
|   | ||||
| @@ -41,6 +41,16 @@ class _$AppRouter extends RootStackRouter { | ||||
|           opaque: true, | ||||
|           barrierDismissible: false); | ||||
|     }, | ||||
|     GalleryViewerRoute.name: (routeData) { | ||||
|       final args = routeData.argsAs<GalleryViewerRouteArgs>(); | ||||
|       return MaterialPageX<dynamic>( | ||||
|           routeData: routeData, | ||||
|           child: GalleryViewerPage( | ||||
|               key: args.key, | ||||
|               assetList: args.assetList, | ||||
|               asset: args.asset, | ||||
|               thumbnailRequestUrl: args.thumbnailRequestUrl)); | ||||
|     }, | ||||
|     ImageViewerRoute.name: (routeData) { | ||||
|       final args = routeData.argsAs<ImageViewerRouteArgs>(); | ||||
|       return MaterialPageX<dynamic>( | ||||
| @@ -50,7 +60,10 @@ class _$AppRouter extends RootStackRouter { | ||||
|               imageUrl: args.imageUrl, | ||||
|               heroTag: args.heroTag, | ||||
|               thumbnailUrl: args.thumbnailUrl, | ||||
|               asset: args.asset)); | ||||
|               asset: args.asset, | ||||
|               authToken: args.authToken, | ||||
|               isZoomedFunction: args.isZoomedFunction, | ||||
|               isZoomedListener: args.isZoomedListener)); | ||||
|     }, | ||||
|     VideoViewerRoute.name: (routeData) { | ||||
|       final args = routeData.argsAs<VideoViewerRouteArgs>(); | ||||
| @@ -174,6 +187,8 @@ class _$AppRouter extends RootStackRouter { | ||||
|                   parent: TabControllerRoute.name, | ||||
|                   guards: [authGuard]) | ||||
|             ]), | ||||
|         RouteConfig(GalleryViewerRoute.name, | ||||
|             path: '/gallery-viewer-page', guards: [authGuard]), | ||||
|         RouteConfig(ImageViewerRoute.name, | ||||
|             path: '/image-viewer-page', guards: [authGuard]), | ||||
|         RouteConfig(VideoViewerRoute.name, | ||||
| @@ -237,6 +252,46 @@ class TabControllerRoute extends PageRouteInfo<void> { | ||||
|   static const String name = 'TabControllerRoute'; | ||||
| } | ||||
|  | ||||
| /// generated route for | ||||
| /// [GalleryViewerPage] | ||||
| class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> { | ||||
|   GalleryViewerRoute( | ||||
|       {Key? key, | ||||
|       required List<AssetResponseDto> assetList, | ||||
|       required AssetResponseDto asset, | ||||
|       required String thumbnailRequestUrl}) | ||||
|       : super(GalleryViewerRoute.name, | ||||
|             path: '/gallery-viewer-page', | ||||
|             args: GalleryViewerRouteArgs( | ||||
|                 key: key, | ||||
|                 assetList: assetList, | ||||
|                 asset: asset, | ||||
|                 thumbnailRequestUrl: thumbnailRequestUrl)); | ||||
|  | ||||
|   static const String name = 'GalleryViewerRoute'; | ||||
| } | ||||
|  | ||||
| class GalleryViewerRouteArgs { | ||||
|   const GalleryViewerRouteArgs( | ||||
|       {this.key, | ||||
|       required this.assetList, | ||||
|       required this.asset, | ||||
|       required this.thumbnailRequestUrl}); | ||||
|  | ||||
|   final Key? key; | ||||
|  | ||||
|   final List<AssetResponseDto> assetList; | ||||
|  | ||||
|   final AssetResponseDto asset; | ||||
|  | ||||
|   final String thumbnailRequestUrl; | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'GalleryViewerRouteArgs{key: $key, assetList: $assetList, asset: $asset, thumbnailRequestUrl: $thumbnailRequestUrl}'; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// generated route for | ||||
| /// [ImageViewerPage] | ||||
| class ImageViewerRoute extends PageRouteInfo<ImageViewerRouteArgs> { | ||||
| @@ -245,7 +300,10 @@ class ImageViewerRoute extends PageRouteInfo<ImageViewerRouteArgs> { | ||||
|       required String imageUrl, | ||||
|       required String heroTag, | ||||
|       required String thumbnailUrl, | ||||
|       required AssetResponseDto asset}) | ||||
|       required AssetResponseDto asset, | ||||
|       required String authToken, | ||||
|       required void Function() isZoomedFunction, | ||||
|       required ValueNotifier<bool> isZoomedListener}) | ||||
|       : super(ImageViewerRoute.name, | ||||
|             path: '/image-viewer-page', | ||||
|             args: ImageViewerRouteArgs( | ||||
| @@ -253,7 +311,10 @@ class ImageViewerRoute extends PageRouteInfo<ImageViewerRouteArgs> { | ||||
|                 imageUrl: imageUrl, | ||||
|                 heroTag: heroTag, | ||||
|                 thumbnailUrl: thumbnailUrl, | ||||
|                 asset: asset)); | ||||
|                 asset: asset, | ||||
|                 authToken: authToken, | ||||
|                 isZoomedFunction: isZoomedFunction, | ||||
|                 isZoomedListener: isZoomedListener)); | ||||
|  | ||||
|   static const String name = 'ImageViewerRoute'; | ||||
| } | ||||
| @@ -264,7 +325,10 @@ class ImageViewerRouteArgs { | ||||
|       required this.imageUrl, | ||||
|       required this.heroTag, | ||||
|       required this.thumbnailUrl, | ||||
|       required this.asset}); | ||||
|       required this.asset, | ||||
|       required this.authToken, | ||||
|       required this.isZoomedFunction, | ||||
|       required this.isZoomedListener}); | ||||
|  | ||||
|   final Key? key; | ||||
|  | ||||
| @@ -276,9 +340,15 @@ class ImageViewerRouteArgs { | ||||
|  | ||||
|   final AssetResponseDto asset; | ||||
|  | ||||
|   final String authToken; | ||||
|  | ||||
|   final void Function() isZoomedFunction; | ||||
|  | ||||
|   final ValueNotifier<bool> isZoomedListener; | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'ImageViewerRouteArgs{key: $key, imageUrl: $imageUrl, heroTag: $heroTag, thumbnailUrl: $thumbnailUrl, asset: $asset}'; | ||||
|     return 'ImageViewerRouteArgs{key: $key, imageUrl: $imageUrl, heroTag: $heroTag, thumbnailUrl: $thumbnailUrl, asset: $asset, authToken: $authToken, isZoomedFunction: $isZoomedFunction, isZoomedListener: $isZoomedListener}'; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -76,69 +76,72 @@ class AssetResponseDto { | ||||
|   SmartInfoResponseDto? smartInfo; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto && | ||||
|      other.type == type && | ||||
|      other.id == id && | ||||
|      other.deviceAssetId == deviceAssetId && | ||||
|      other.ownerId == ownerId && | ||||
|      other.deviceId == deviceId && | ||||
|      other.originalPath == originalPath && | ||||
|      other.resizePath == resizePath && | ||||
|      other.createdAt == createdAt && | ||||
|      other.modifiedAt == modifiedAt && | ||||
|      other.isFavorite == isFavorite && | ||||
|      other.mimeType == mimeType && | ||||
|      other.duration == duration && | ||||
|      other.webpPath == webpPath && | ||||
|      other.encodedVideoPath == encodedVideoPath && | ||||
|      other.exifInfo == exifInfo && | ||||
|      other.smartInfo == smartInfo; | ||||
|   bool operator ==(Object other) => | ||||
|       identical(this, other) || | ||||
|       other is AssetResponseDto && | ||||
|           other.type == type && | ||||
|           other.id == id && | ||||
|           other.deviceAssetId == deviceAssetId && | ||||
|           other.ownerId == ownerId && | ||||
|           other.deviceId == deviceId && | ||||
|           other.originalPath == originalPath && | ||||
|           other.resizePath == resizePath && | ||||
|           other.createdAt == createdAt && | ||||
|           other.modifiedAt == modifiedAt && | ||||
|           other.isFavorite == isFavorite && | ||||
|           other.mimeType == mimeType && | ||||
|           other.duration == duration && | ||||
|           other.webpPath == webpPath && | ||||
|           other.encodedVideoPath == encodedVideoPath && | ||||
|           other.exifInfo == exifInfo && | ||||
|           other.smartInfo == smartInfo; | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (type.hashCode) + | ||||
|     (id.hashCode) + | ||||
|     (deviceAssetId.hashCode) + | ||||
|     (ownerId.hashCode) + | ||||
|     (deviceId.hashCode) + | ||||
|     (originalPath.hashCode) + | ||||
|     (resizePath == null ? 0 : resizePath!.hashCode) + | ||||
|     (createdAt.hashCode) + | ||||
|     (modifiedAt.hashCode) + | ||||
|     (isFavorite.hashCode) + | ||||
|     (mimeType == null ? 0 : mimeType!.hashCode) + | ||||
|     (duration.hashCode) + | ||||
|     (webpPath == null ? 0 : webpPath!.hashCode) + | ||||
|     (encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) + | ||||
|     (exifInfo == null ? 0 : exifInfo!.hashCode) + | ||||
|     (smartInfo == null ? 0 : smartInfo!.hashCode); | ||||
|       // ignore: unnecessary_parenthesis | ||||
|       (type.hashCode) + | ||||
|       (id.hashCode) + | ||||
|       (deviceAssetId.hashCode) + | ||||
|       (ownerId.hashCode) + | ||||
|       (deviceId.hashCode) + | ||||
|       (originalPath.hashCode) + | ||||
|       (resizePath == null ? 0 : resizePath!.hashCode) + | ||||
|       (createdAt.hashCode) + | ||||
|       (modifiedAt.hashCode) + | ||||
|       (isFavorite.hashCode) + | ||||
|       (mimeType == null ? 0 : mimeType!.hashCode) + | ||||
|       (duration.hashCode) + | ||||
|       (webpPath == null ? 0 : webpPath!.hashCode) + | ||||
|       (encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) + | ||||
|       (exifInfo == null ? 0 : exifInfo!.hashCode) + | ||||
|       (smartInfo == null ? 0 : smartInfo!.hashCode); | ||||
|  | ||||
|   @override | ||||
|   String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo]'; | ||||
|   String toString() => | ||||
|       'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo]'; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final _json = <String, dynamic>{}; | ||||
|       _json[r'type'] = type; | ||||
|       _json[r'id'] = id; | ||||
|       _json[r'deviceAssetId'] = deviceAssetId; | ||||
|       _json[r'ownerId'] = ownerId; | ||||
|       _json[r'deviceId'] = deviceId; | ||||
|       _json[r'originalPath'] = originalPath; | ||||
|     _json[r'type'] = type; | ||||
|     _json[r'id'] = id; | ||||
|     _json[r'deviceAssetId'] = deviceAssetId; | ||||
|     _json[r'ownerId'] = ownerId; | ||||
|     _json[r'deviceId'] = deviceId; | ||||
|     _json[r'originalPath'] = originalPath; | ||||
|     if (resizePath != null) { | ||||
|       _json[r'resizePath'] = resizePath; | ||||
|     } else { | ||||
|       _json[r'resizePath'] = null; | ||||
|     } | ||||
|       _json[r'createdAt'] = createdAt; | ||||
|       _json[r'modifiedAt'] = modifiedAt; | ||||
|       _json[r'isFavorite'] = isFavorite; | ||||
|     _json[r'createdAt'] = createdAt; | ||||
|     _json[r'modifiedAt'] = modifiedAt; | ||||
|     _json[r'isFavorite'] = isFavorite; | ||||
|     if (mimeType != null) { | ||||
|       _json[r'mimeType'] = mimeType; | ||||
|     } else { | ||||
|       _json[r'mimeType'] = null; | ||||
|     } | ||||
|       _json[r'duration'] = duration; | ||||
|     _json[r'duration'] = duration; | ||||
|     if (webpPath != null) { | ||||
|       _json[r'webpPath'] = webpPath; | ||||
|     } else { | ||||
| @@ -174,8 +177,10 @@ class AssetResponseDto { | ||||
|       // Note 2: this code is stripped in release mode! | ||||
|       assert(() { | ||||
|         requiredKeys.forEach((key) { | ||||
|           assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.'); | ||||
|           assert(json.containsKey(key), | ||||
|               'Required key "AssetResponseDto[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, | ||||
|               'Required key "AssetResponseDto[$key]" has a null value in JSON.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
| @@ -202,7 +207,10 @@ class AssetResponseDto { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static List<AssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|   static List<AssetResponseDto>? listFromJson( | ||||
|     dynamic json, { | ||||
|     bool growable = false, | ||||
|   }) { | ||||
|     final result = <AssetResponseDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
| @@ -230,12 +238,18 @@ class AssetResponseDto { | ||||
|   } | ||||
|  | ||||
|   // maps a json object with a list of AssetResponseDto-objects as value to a dart map | ||||
|   static Map<String, List<AssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|   static Map<String, List<AssetResponseDto>> mapListFromJson( | ||||
|     dynamic json, { | ||||
|     bool growable = false, | ||||
|   }) { | ||||
|     final map = <String, List<AssetResponseDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = AssetResponseDto.listFromJson(entry.value, growable: growable,); | ||||
|         final value = AssetResponseDto.listFromJson( | ||||
|           entry.value, | ||||
|           growable: growable, | ||||
|         ); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
| @@ -262,4 +276,3 @@ class AssetResponseDto { | ||||
|     'encodedVideoPath', | ||||
|   }; | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user