mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
refactor(mobile): reworked Asset, store all required fields from local & remote (#1539)
replace usage of AssetResponseDto with Asset Add new class ExifInfo to store data from ExifResponseDto
This commit is contained in:
committed by
GitHub
parent
7bd2455175
commit
0048662182
@@ -13,7 +13,7 @@ import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_s
|
||||
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/video_viewer_page.dart';
|
||||
import 'package:immich_mobile/modules/home/services/asset.service.dart';
|
||||
import 'package:immich_mobile/shared/services/asset.service.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart';
|
||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
@@ -80,31 +80,34 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Thumbnail image of a remote asset. Required asset.remote != null
|
||||
ImageProvider remoteThumbnailImageProvider(Asset asset, api.ThumbnailFormat type) {
|
||||
/// Thumbnail image of a remote asset. Required asset.isRemote
|
||||
ImageProvider remoteThumbnailImageProvider(
|
||||
Asset asset,
|
||||
api.ThumbnailFormat type,
|
||||
) {
|
||||
return CachedNetworkImageProvider(
|
||||
getThumbnailUrl(
|
||||
asset.remote!,
|
||||
asset,
|
||||
type: type,
|
||||
),
|
||||
cacheKey: getThumbnailCacheKey(
|
||||
asset.remote!,
|
||||
asset,
|
||||
type: type,
|
||||
),
|
||||
headers: {"Authorization": authToken},
|
||||
);
|
||||
}
|
||||
|
||||
/// Original (large) image of a remote asset. Required asset.remote != null
|
||||
/// Original (large) image of a remote asset. Required asset.isRemote
|
||||
ImageProvider originalImageProvider(Asset asset) {
|
||||
return CachedNetworkImageProvider(
|
||||
getImageUrl(asset.remote!),
|
||||
cacheKey: getImageCacheKey(asset.remote!),
|
||||
getImageUrl(asset),
|
||||
cacheKey: getImageCacheKey(asset),
|
||||
headers: {"Authorization": authToken},
|
||||
);
|
||||
}
|
||||
|
||||
/// Thumbnail image of a local asset. Required asset.local != null
|
||||
/// Thumbnail image of a local asset. Required asset.isLocal
|
||||
ImageProvider localThumbnailImageProvider(Asset asset) {
|
||||
return AssetEntityImageProvider(
|
||||
asset.local!,
|
||||
@@ -114,10 +117,9 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
MediaQuery.of(context).size.height.floor(),
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// Original (large) image of a local asset. Required asset.local != null
|
||||
/// Original (large) image of a local asset. Required asset.isLocal
|
||||
ImageProvider localImageProvider(Asset asset) {
|
||||
return AssetEntityImageProvider(asset.local!);
|
||||
}
|
||||
@@ -132,7 +134,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
// Probably load WEBP either way
|
||||
precacheImage(
|
||||
remoteThumbnailImageProvider(
|
||||
asset,
|
||||
asset,
|
||||
api.ThumbnailFormat.WEBP,
|
||||
),
|
||||
context,
|
||||
@@ -154,26 +156,23 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void showInfo() {
|
||||
if (assetList[indexOfAsset.value].isRemote) {
|
||||
showModalBottomSheet(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15.0),
|
||||
),
|
||||
barrierColor: Colors.transparent,
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ExifBottomSheet(assetDetail: assetDetail!);
|
||||
},
|
||||
);
|
||||
}
|
||||
showModalBottomSheet(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15.0),
|
||||
),
|
||||
barrierColor: Colors.transparent,
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ExifBottomSheet(assetDetail: assetDetail!);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void handleDelete(Asset deleteAsset) {
|
||||
@@ -244,7 +243,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
? null
|
||||
: () {
|
||||
ref.watch(imageViewerStateProvider.notifier).downloadAsset(
|
||||
assetList[indexOfAsset.value].remote!,
|
||||
assetList[indexOfAsset.value],
|
||||
context,
|
||||
);
|
||||
},
|
||||
@@ -256,8 +255,10 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
onToggleMotionVideo: (() {
|
||||
isPlayingMotionVideo.value = !isPlayingMotionVideo.value;
|
||||
}),
|
||||
onDeletePressed: () => handleDelete((assetList[indexOfAsset.value])),
|
||||
onAddToAlbumPressed: () => addToAlbum(assetList[indexOfAsset.value]),
|
||||
onDeletePressed: () =>
|
||||
handleDelete((assetList[indexOfAsset.value])),
|
||||
onAddToAlbumPressed: () =>
|
||||
addToAlbum(assetList[indexOfAsset.value]),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -268,117 +269,132 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
body: Stack(
|
||||
children: [
|
||||
PhotoViewGallery.builder(
|
||||
scaleStateChangedCallback: (state) {
|
||||
isZoomed.value = state != PhotoViewScaleState.initial;
|
||||
showAppBar.value = !isZoomed.value;
|
||||
},
|
||||
pageController: controller,
|
||||
scrollPhysics: isZoomed.value
|
||||
? const NeverScrollableScrollPhysics() // Don't allow paging while scrolled in
|
||||
: (Platform.isIOS
|
||||
? const BouncingScrollPhysics() // Use bouncing physics for iOS
|
||||
: const ClampingScrollPhysics() // Use heavy physics for Android
|
||||
),
|
||||
itemCount: assetList.length,
|
||||
scrollDirection: Axis.horizontal,
|
||||
onPageChanged: (value) {
|
||||
// Precache image
|
||||
if (indexOfAsset.value < value) {
|
||||
// Moving forwards, so precache the next asset
|
||||
precacheNextImage(value + 1);
|
||||
} else {
|
||||
// Moving backwards, so precache previous asset
|
||||
precacheNextImage(value - 1);
|
||||
}
|
||||
indexOfAsset.value = value;
|
||||
HapticFeedback.selectionClick();
|
||||
},
|
||||
loadingBuilder: isLoadPreview.value ? (context, event) {
|
||||
final asset = assetList[indexOfAsset.value];
|
||||
if (!asset.isLocal) {
|
||||
// Use the WEBP Thumbnail as a placeholder for the JPEG thumbnail to acheive
|
||||
// Three-Stage Loading (WEBP -> JPEG -> Original)
|
||||
final webPThumbnail = CachedNetworkImage(
|
||||
imageUrl: getThumbnailUrl(asset.remote!, type: api.ThumbnailFormat.WEBP),
|
||||
cacheKey: getThumbnailCacheKey(asset.remote!, type: api.ThumbnailFormat.WEBP),
|
||||
httpHeaders: { 'Authorization': authToken },
|
||||
progressIndicatorBuilder: (_, __, ___) => const Center(child: ImmichLoadingIndicator(),),
|
||||
fadeInDuration: const Duration(milliseconds: 0),
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
|
||||
return CachedNetworkImage(
|
||||
imageUrl: getThumbnailUrl(asset.remote!, type: api.ThumbnailFormat.JPEG),
|
||||
cacheKey: getThumbnailCacheKey(asset.remote!, type: api.ThumbnailFormat.JPEG),
|
||||
httpHeaders: { 'Authorization': authToken },
|
||||
fit: BoxFit.contain,
|
||||
fadeInDuration: const Duration(milliseconds: 0),
|
||||
placeholder: (_, __) => webPThumbnail,
|
||||
);
|
||||
} else {
|
||||
return Image(
|
||||
image: localThumbnailImageProvider(asset),
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
}
|
||||
} : null,
|
||||
builder: (context, index) {
|
||||
getAssetExif();
|
||||
if (assetList[index].isImage && !isPlayingMotionVideo.value) {
|
||||
// Show photo
|
||||
final ImageProvider provider;
|
||||
if (assetList[index].isLocal) {
|
||||
provider = localImageProvider(assetList[index]);
|
||||
} else {
|
||||
if (isLoadOriginal.value) {
|
||||
provider = originalImageProvider(assetList[index]);
|
||||
} else {
|
||||
provider = remoteThumbnailImageProvider(
|
||||
assetList[index],
|
||||
api.ThumbnailFormat.JPEG,
|
||||
);
|
||||
}
|
||||
}
|
||||
return PhotoViewGalleryPageOptions(
|
||||
onDragStart: (_, details, __) => localPosition = details.localPosition,
|
||||
onDragUpdate: (_, details, __) => handleSwipeUpDown(details),
|
||||
onTapDown: (_, __, ___) => showAppBar.value = !showAppBar.value,
|
||||
imageProvider: provider,
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: assetList[index].id),
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
);
|
||||
} else {
|
||||
return PhotoViewGalleryPageOptions.customChild(
|
||||
onDragStart: (_, details, __) => localPosition = details.localPosition,
|
||||
onDragUpdate: (_, details, __) => handleSwipeUpDown(details),
|
||||
onTapDown: (_, __, ___) => showAppBar.value = !showAppBar.value,
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: assetList[index].id),
|
||||
maxScale: 1.0,
|
||||
minScale: 1.0,
|
||||
child: SafeArea(
|
||||
child: VideoViewerPage(
|
||||
asset: assetList[index],
|
||||
isMotionVideo: isPlayingMotionVideo.value,
|
||||
onVideoEnded: () {
|
||||
if (isPlayingMotionVideo.value) {
|
||||
isPlayingMotionVideo.value = false;
|
||||
}
|
||||
},
|
||||
),
|
||||
scaleStateChangedCallback: (state) {
|
||||
isZoomed.value = state != PhotoViewScaleState.initial;
|
||||
showAppBar.value = !isZoomed.value;
|
||||
},
|
||||
pageController: controller,
|
||||
scrollPhysics: isZoomed.value
|
||||
? const NeverScrollableScrollPhysics() // Don't allow paging while scrolled in
|
||||
: (Platform.isIOS
|
||||
? const BouncingScrollPhysics() // Use bouncing physics for iOS
|
||||
: const ClampingScrollPhysics() // Use heavy physics for Android
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: buildAppBar(),
|
||||
),
|
||||
],
|
||||
itemCount: assetList.length,
|
||||
scrollDirection: Axis.horizontal,
|
||||
onPageChanged: (value) {
|
||||
// Precache image
|
||||
if (indexOfAsset.value < value) {
|
||||
// Moving forwards, so precache the next asset
|
||||
precacheNextImage(value + 1);
|
||||
} else {
|
||||
// Moving backwards, so precache previous asset
|
||||
precacheNextImage(value - 1);
|
||||
}
|
||||
indexOfAsset.value = value;
|
||||
HapticFeedback.selectionClick();
|
||||
},
|
||||
loadingBuilder: isLoadPreview.value
|
||||
? (context, event) {
|
||||
final asset = assetList[indexOfAsset.value];
|
||||
if (!asset.isLocal) {
|
||||
// Use the WEBP Thumbnail as a placeholder for the JPEG thumbnail to acheive
|
||||
// Three-Stage Loading (WEBP -> JPEG -> Original)
|
||||
final webPThumbnail = CachedNetworkImage(
|
||||
imageUrl: getThumbnailUrl(asset),
|
||||
cacheKey: getThumbnailCacheKey(asset),
|
||||
httpHeaders: {'Authorization': authToken},
|
||||
progressIndicatorBuilder: (_, __, ___) => const Center(
|
||||
child: ImmichLoadingIndicator(),
|
||||
),
|
||||
fadeInDuration: const Duration(milliseconds: 0),
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
|
||||
return CachedNetworkImage(
|
||||
imageUrl: getThumbnailUrl(
|
||||
asset,
|
||||
type: api.ThumbnailFormat.JPEG,
|
||||
),
|
||||
cacheKey: getThumbnailCacheKey(
|
||||
asset,
|
||||
type: api.ThumbnailFormat.JPEG,
|
||||
),
|
||||
httpHeaders: {'Authorization': authToken},
|
||||
fit: BoxFit.contain,
|
||||
fadeInDuration: const Duration(milliseconds: 0),
|
||||
placeholder: (_, __) => webPThumbnail,
|
||||
);
|
||||
} else {
|
||||
return Image(
|
||||
image: localThumbnailImageProvider(asset),
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
builder: (context, index) {
|
||||
getAssetExif();
|
||||
if (assetList[index].isImage && !isPlayingMotionVideo.value) {
|
||||
// Show photo
|
||||
final ImageProvider provider;
|
||||
if (assetList[index].isLocal) {
|
||||
provider = localImageProvider(assetList[index]);
|
||||
} else {
|
||||
if (isLoadOriginal.value) {
|
||||
provider = originalImageProvider(assetList[index]);
|
||||
} else {
|
||||
provider = remoteThumbnailImageProvider(
|
||||
assetList[index],
|
||||
api.ThumbnailFormat.JPEG,
|
||||
);
|
||||
}
|
||||
}
|
||||
return PhotoViewGalleryPageOptions(
|
||||
onDragStart: (_, details, __) =>
|
||||
localPosition = details.localPosition,
|
||||
onDragUpdate: (_, details, __) => handleSwipeUpDown(details),
|
||||
onTapDown: (_, __, ___) =>
|
||||
showAppBar.value = !showAppBar.value,
|
||||
imageProvider: provider,
|
||||
heroAttributes:
|
||||
PhotoViewHeroAttributes(tag: assetList[index].id),
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
);
|
||||
} else {
|
||||
return PhotoViewGalleryPageOptions.customChild(
|
||||
onDragStart: (_, details, __) =>
|
||||
localPosition = details.localPosition,
|
||||
onDragUpdate: (_, details, __) => handleSwipeUpDown(details),
|
||||
onTapDown: (_, __, ___) =>
|
||||
showAppBar.value = !showAppBar.value,
|
||||
heroAttributes:
|
||||
PhotoViewHeroAttributes(tag: assetList[index].id),
|
||||
maxScale: 1.0,
|
||||
minScale: 1.0,
|
||||
child: SafeArea(
|
||||
child: VideoViewerPage(
|
||||
asset: assetList[index],
|
||||
isMotionVideo: isPlayingMotionVideo.value,
|
||||
onVideoEnded: () {
|
||||
if (isPlayingMotionVideo.value) {
|
||||
isPlayingMotionVideo.value = false;
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: buildAppBar(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ class VideoViewerPage extends HookConsumerWidget {
|
||||
final box = Hive.box(userInfoBox);
|
||||
final String jwtToken = box.get(accessTokenKey);
|
||||
final String videoUrl = isMotionVideo
|
||||
? '${box.get(serverEndpointKey)}/asset/file/${asset.remote?.livePhotoVideoId!}'
|
||||
: '${box.get(serverEndpointKey)}/asset/file/${asset.id}';
|
||||
? '${box.get(serverEndpointKey)}/asset/file/${asset.livePhotoVideoId}'
|
||||
: '${box.get(serverEndpointKey)}/asset/file/${asset.remoteId}';
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
|
||||
Reference in New Issue
Block a user