Refactor mobile to use OpenApi generated SDK (#336)

This commit is contained in:
Alex
2022-07-13 07:23:48 -05:00
committed by GitHub
parent d69470e207
commit ae7e582ec8
276 changed files with 14513 additions and 3003 deletions

View File

@@ -3,17 +3,20 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart';
import 'package:immich_mobile/modules/asset_viewer/services/image_viewer.service.dart';
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:openapi/api.dart';
class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
final ImageViewerService _imageViewerService = ImageViewerService();
final ImageViewerService _imageViewerService;
ImageViewerStateNotifier()
: super(ImageViewerPageState(
downloadAssetStatus: DownloadAssetStatus.idle));
ImageViewerStateNotifier(this._imageViewerService)
: super(
ImageViewerPageState(
downloadAssetStatus: DownloadAssetStatus.idle,
),
);
void downloadAsset(ImmichAsset asset, BuildContext context) async {
void downloadAsset(AssetResponseDto asset, BuildContext context) async {
state = state.copyWith(downloadAssetStatus: DownloadAssetStatus.loading);
bool isSuccess = await _imageViewerService.downloadAssetToDevice(asset);
@@ -43,4 +46,5 @@ class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
final imageViewerStateProvider =
StateNotifierProvider<ImageViewerStateNotifier, ImageViewerPageState>(
((ref) => ImageViewerStateNotifier()));
((ref) => ImageViewerStateNotifier(ref.watch(imageViewerServiceProvider))),
);

View File

@@ -1,33 +1,35 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:openapi/api.dart';
import 'package:path/path.dart' as p;
import 'package:http/http.dart' as http;
import 'package:photo_manager/photo_manager.dart';
import 'package:path_provider/path_provider.dart';
final imageViewerServiceProvider =
Provider((ref) => ImageViewerService(ref.watch(apiServiceProvider)));
class ImageViewerService {
Future<bool> downloadAssetToDevice(ImmichAsset asset) async {
final ApiService _apiService;
ImageViewerService(this._apiService);
Future<bool> downloadAssetToDevice(AssetResponseDto asset) async {
try {
String fileName = p.basename(asset.originalPath);
var savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey);
Uri filePath = Uri.parse(
"$savedEndpoint/asset/download?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false");
var res = await http.get(
filePath,
headers: {
"Authorization": "Bearer ${Hive.box(userInfoBox).get(accessTokenKey)}"
},
var res = await _apiService.assetApi.downloadFileWithHttpInfo(
asset.deviceAssetId,
asset.deviceId,
isThumb: false,
isWeb: false,
);
final AssetEntity? entity;
if (asset.type == 'IMAGE') {
if (asset.type == AssetTypeEnum.IMAGE) {
entity = await PhotoManager.editor.saveImage(
res.bodyBytes,
title: p.basename(asset.originalPath),

View File

@@ -2,13 +2,12 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/immich_asset_with_exif.model.dart';
import 'package:intl/intl.dart';
import 'package:openapi/api.dart';
import 'package:path/path.dart' as p;
import 'package:latlong2/latlong.dart';
class ExifBottomSheet extends ConsumerWidget {
final ImmichAssetWithExif assetDetail;
final AssetResponseDto assetDetail;
const ExifBottomSheet({Key? key, required this.assetDetail})
: super(key: key);
@@ -26,8 +25,10 @@ class ExifBottomSheet extends ConsumerWidget {
),
child: FlutterMap(
options: MapOptions(
center: LatLng(assetDetail.exifInfo!.latitude!,
assetDetail.exifInfo!.longitude!),
center: LatLng(
assetDetail.exifInfo?.latitude?.toDouble() ?? 0,
assetDetail.exifInfo?.longitude?.toDouble() ?? 0,
),
zoom: 16.0,
),
layers: [
@@ -46,10 +47,13 @@ class ExifBottomSheet extends ConsumerWidget {
markers: [
Marker(
anchorPos: AnchorPos.align(AnchorAlign.top),
point: LatLng(assetDetail.exifInfo!.latitude!,
assetDetail.exifInfo!.longitude!),
point: LatLng(
assetDetail.exifInfo?.latitude?.toDouble() ?? 0,
assetDetail.exifInfo?.longitude?.toDouble() ?? 0,
),
builder: (ctx) => const Image(
image: AssetImage('assets/location-pin.png')),
image: AssetImage('assets/location-pin.png'),
),
),
],
),
@@ -63,7 +67,10 @@ class ExifBottomSheet extends ConsumerWidget {
return Text(
"${assetDetail.exifInfo!.city}, ${assetDetail.exifInfo!.state}",
style: TextStyle(
fontSize: 12, color: Colors.grey[200], fontWeight: FontWeight.bold),
fontSize: 12,
color: Colors.grey[200],
fontWeight: FontWeight.bold,
),
);
}
@@ -74,7 +81,7 @@ class ExifBottomSheet extends ConsumerWidget {
if (assetDetail.exifInfo?.dateTimeOriginal != null)
Text(
DateFormat('date_format'.tr()).format(
DateTime.parse(assetDetail.exifInfo!.dateTimeOriginal!),
assetDetail.exifInfo!.dateTimeOriginal!,
),
style: TextStyle(
color: Colors.grey[400],
@@ -151,7 +158,8 @@ class ExifBottomSheet extends ConsumerWidget {
),
subtitle: assetDetail.exifInfo?.exifImageHeight != null
? Text(
"${assetDetail.exifInfo?.exifImageHeight} x ${assetDetail.exifInfo?.exifImageWidth} ${assetDetail.exifInfo?.fileSizeInByte!}B ")
"${assetDetail.exifInfo?.exifImageHeight} x ${assetDetail.exifInfo?.exifImageWidth} ${assetDetail.exifInfo?.fileSizeInByte!}B ",
)
: null,
),
if (assetDetail.exifInfo?.make != null)
@@ -166,7 +174,8 @@ class ExifBottomSheet extends ConsumerWidget {
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
"ƒ/${assetDetail.exifInfo?.fNumber} 1/${(1 / (assetDetail.exifInfo?.exposureTime ?? 1)).toStringAsFixed(0)} ${assetDetail.exifInfo?.focalLength}mm ISO${assetDetail.exifInfo?.iso} "),
"ƒ/${assetDetail.exifInfo?.fNumber} 1/${(1 / (assetDetail.exifInfo?.exposureTime ?? 1)).toStringAsFixed(0)} ${assetDetail.exifInfo?.focalLength}mm ISO${assetDetail.exifInfo?.iso} ",
),
),
],
),

View File

@@ -17,16 +17,20 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
bool allowMoving = _status == _RemoteImageStatus.full;
return PhotoView(
imageProvider: _imageProvider,
minScale: PhotoViewComputedScale.contained,
maxScale: allowMoving ? 1.0 : PhotoViewComputedScale.contained,
enablePanAlways: true,
scaleStateChangedCallback: _scaleStateChanged,
onScaleEnd: _onScaleListener);
imageProvider: _imageProvider,
minScale: PhotoViewComputedScale.contained,
maxScale: allowMoving ? 1.0 : PhotoViewComputedScale.contained,
enablePanAlways: true,
scaleStateChangedCallback: _scaleStateChanged,
onScaleEnd: _onScaleListener,
);
}
void _onScaleListener(BuildContext context, ScaleEndDetails details,
PhotoViewControllerValue controllerValue) {
void _onScaleListener(
BuildContext context,
ScaleEndDetails details,
PhotoViewControllerValue controllerValue,
) {
// Disable swipe events when zoomed in
if (_zoomedIn) return;
@@ -42,12 +46,17 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
}
CachedNetworkImageProvider _authorizedImageProvider(String url) {
return CachedNetworkImageProvider(url,
headers: {"Authorization": widget.authToken}, cacheKey: url);
return CachedNetworkImageProvider(
url,
headers: {"Authorization": widget.authToken},
cacheKey: url,
);
}
void _performStateTransition(
_RemoteImageStatus newStatus, CachedNetworkImageProvider provider) {
_RemoteImageStatus newStatus,
CachedNetworkImageProvider provider,
) {
// Transition to same status is forbidden
if (_status == newStatus) return;
// Transition full -> thumbnail is forbidden
@@ -67,19 +76,22 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
_authorizedImageProvider(widget.thumbnailUrl);
_imageProvider = thumbnailProvider;
thumbnailProvider
.resolve(const ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo imageInfo, _) {
_performStateTransition(_RemoteImageStatus.thumbnail, thumbnailProvider);
}));
thumbnailProvider.resolve(const ImageConfiguration()).addListener(
ImageStreamListener((ImageInfo imageInfo, _) {
_performStateTransition(
_RemoteImageStatus.thumbnail,
thumbnailProvider,
);
}),
);
CachedNetworkImageProvider fullProvider =
_authorizedImageProvider(widget.imageUrl);
fullProvider
.resolve(const ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo imageInfo, _) {
_performStateTransition(_RemoteImageStatus.full, fullProvider);
}));
fullProvider.resolve(const ImageConfiguration()).addListener(
ImageStreamListener((ImageInfo imageInfo, _) {
_performStateTransition(_RemoteImageStatus.full, fullProvider);
}),
);
}
@override
@@ -90,14 +102,14 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
}
class RemotePhotoView extends StatefulWidget {
const RemotePhotoView(
{Key? key,
required this.thumbnailUrl,
required this.imageUrl,
required this.authToken,
required this.onSwipeDown,
required this.onSwipeUp})
: super(key: key);
const RemotePhotoView({
Key? key,
required this.thumbnailUrl,
required this.imageUrl,
required this.authToken,
required this.onSwipeDown,
required this.onSwipeUp,
}) : super(key: key);
final String thumbnailUrl;
final String imageUrl;

View File

@@ -3,17 +3,17 @@ import 'dart:developer';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:openapi/api.dart';
class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
const TopControlAppBar(
{Key? key,
required this.asset,
required this.onMoreInfoPressed,
required this.onDownloadPressed})
: super(key: key);
const TopControlAppBar({
Key? key,
required this.asset,
required this.onMoreInfoPressed,
required this.onDownloadPressed,
}) : super(key: key);
final ImmichAsset asset;
final AssetResponseDto asset;
final Function onMoreInfoPressed;
final Function onDownloadPressed;
@@ -54,12 +54,13 @@ class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
: const Icon(Icons.favorite_border_rounded),
),
IconButton(
iconSize: iconSize,
splashRadius: iconSize,
onPressed: () {
onMoreInfoPressed();
},
icon: const Icon(Icons.more_horiz_rounded))
iconSize: iconSize,
splashRadius: iconSize,
onPressed: () {
onMoreInfoPressed();
},
icon: const Icon(Icons.more_horiz_rounded),
)
],
);
}

View File

@@ -11,17 +11,16 @@ 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:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:immich_mobile/shared/models/immich_asset_with_exif.model.dart';
import 'package:openapi/api.dart';
// ignore: must_be_immutable
class ImageViewerPage extends HookConsumerWidget {
final String imageUrl;
final String heroTag;
final String thumbnailUrl;
final ImmichAsset asset;
final AssetResponseDto asset;
ImmichAssetWithExif? assetDetail;
AssetResponseDto? assetDetail;
ImageViewerPage({
Key? key,
@@ -54,10 +53,13 @@ class ImageViewerPage extends HookConsumerWidget {
);
}
useEffect(() {
getAssetExif();
return null;
}, []);
useEffect(
() {
getAssetExif();
return null;
},
[],
);
return Scaffold(
backgroundColor: Colors.black,
@@ -75,14 +77,15 @@ class ImageViewerPage extends HookConsumerWidget {
children: [
Center(
child: Hero(
tag: heroTag,
child: RemotePhotoView(
thumbnailUrl: thumbnailUrl,
imageUrl: imageUrl,
authToken: "Bearer ${box.get(accessTokenKey)}",
onSwipeDown: () => AutoRouter.of(context).pop(),
onSwipeUp: () => showInfo(),
)),
tag: heroTag,
child: RemotePhotoView(
thumbnailUrl: thumbnailUrl,
imageUrl: imageUrl,
authToken: "Bearer ${box.get(accessTokenKey)}",
onSwipeDown: () => AutoRouter.of(context).pop(),
onSwipeUp: () => showInfo(),
),
),
),
if (downloadAssetStatus == DownloadAssetStatus.loading)
const Center(

View File

@@ -12,15 +12,14 @@ import 'package:immich_mobile/modules/asset_viewer/ui/download_loading_indicator
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:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:immich_mobile/shared/models/immich_asset_with_exif.model.dart';
import 'package:openapi/api.dart';
import 'package:video_player/video_player.dart';
// ignore: must_be_immutable
class VideoViewerPage extends HookConsumerWidget {
final String videoUrl;
final ImmichAsset asset;
ImmichAssetWithExif? assetDetail;
final AssetResponseDto asset;
AssetResponseDto? assetDetail;
VideoViewerPage({Key? key, required this.videoUrl, required this.asset})
: super(key: key);
@@ -49,10 +48,13 @@ class VideoViewerPage extends HookConsumerWidget {
await ref.watch(assetServiceProvider).getAssetById(asset.id);
}
useEffect(() {
getAssetExif();
return null;
}, []);
useEffect(
() {
getAssetExif();
return null;
},
[],
);
return Scaffold(
backgroundColor: Colors.black,
@@ -116,8 +118,10 @@ class _VideoThumbnailPlayerState extends State<VideoThumbnailPlayer> {
Future<void> initializePlayer() async {
try {
videoPlayerController = VideoPlayerController.network(widget.url,
httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"});
videoPlayerController = VideoPlayerController.network(
widget.url,
httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"},
);
await videoPlayerController.initialize();
_createChewieController();