mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
Download asset to local and error fixing (#100)
* Update photo_manager pub package * Added download endpoint for assets * Successfully save a photo to the local device's gallery * Save save a video to the local device's gallery * Fixed #97 * Added download loading indicator * Refactor and increase the font size for curated search thumbnail images * Reposition loading animation on the search result page
This commit is contained in:
@@ -10,6 +10,8 @@ class ImmichAsset {
|
||||
final String modifiedAt;
|
||||
final bool isFavorite;
|
||||
final String? duration;
|
||||
final String originalPath;
|
||||
final String resizePath;
|
||||
|
||||
ImmichAsset({
|
||||
required this.id,
|
||||
@@ -21,6 +23,8 @@ class ImmichAsset {
|
||||
required this.modifiedAt,
|
||||
required this.isFavorite,
|
||||
this.duration,
|
||||
required this.originalPath,
|
||||
required this.resizePath,
|
||||
});
|
||||
|
||||
ImmichAsset copyWith({
|
||||
@@ -33,6 +37,8 @@ class ImmichAsset {
|
||||
String? modifiedAt,
|
||||
bool? isFavorite,
|
||||
String? duration,
|
||||
String? originalPath,
|
||||
String? resizePath,
|
||||
}) {
|
||||
return ImmichAsset(
|
||||
id: id ?? this.id,
|
||||
@@ -44,6 +50,8 @@ class ImmichAsset {
|
||||
modifiedAt: modifiedAt ?? this.modifiedAt,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
duration: duration ?? this.duration,
|
||||
originalPath: originalPath ?? this.originalPath,
|
||||
resizePath: resizePath ?? this.resizePath,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,6 +66,8 @@ class ImmichAsset {
|
||||
'modifiedAt': modifiedAt,
|
||||
'isFavorite': isFavorite,
|
||||
'duration': duration,
|
||||
'originalPath': originalPath,
|
||||
'resizePath': resizePath,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,6 +82,8 @@ class ImmichAsset {
|
||||
modifiedAt: map['modifiedAt'] ?? '',
|
||||
isFavorite: map['isFavorite'] ?? false,
|
||||
duration: map['duration'],
|
||||
originalPath: map['originalPath'] ?? '',
|
||||
resizePath: map['resizePath'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,7 +93,7 @@ class ImmichAsset {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ImmichAsset(id: $id, deviceAssetId: $deviceAssetId, userId: $userId, deviceId: $deviceId, type: $type, createdAt: $createdAt, modifiedAt: $modifiedAt, isFavorite: $isFavorite, duration: $duration)';
|
||||
return 'ImmichAsset(id: $id, deviceAssetId: $deviceAssetId, userId: $userId, deviceId: $deviceId, type: $type, createdAt: $createdAt, modifiedAt: $modifiedAt, isFavorite: $isFavorite, duration: $duration, originalPath: $originalPath, resizePath: $resizePath)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -97,7 +109,9 @@ class ImmichAsset {
|
||||
other.createdAt == createdAt &&
|
||||
other.modifiedAt == modifiedAt &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.duration == duration;
|
||||
other.duration == duration &&
|
||||
other.originalPath == originalPath &&
|
||||
other.resizePath == resizePath;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -110,6 +124,8 @@ class ImmichAsset {
|
||||
createdAt.hashCode ^
|
||||
modifiedAt.hashCode ^
|
||||
isFavorite.hashCode ^
|
||||
duration.hashCode;
|
||||
duration.hashCode ^
|
||||
originalPath.hashCode ^
|
||||
resizePath.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ class BackupService {
|
||||
});
|
||||
|
||||
// Build thumbnail multipart data
|
||||
var thumbnailData = await entity.thumbDataWithSize(1280, 720);
|
||||
var thumbnailData = await entity.thumbnailDataWithSize(const ThumbnailSize(720, 1280));
|
||||
if (thumbnailData != null) {
|
||||
thumbnailUploadData = MultipartFile.fromBytes(
|
||||
List.from(thumbnailData),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
@@ -25,16 +26,36 @@ class NetworkService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> getRequest({required String url}) async {
|
||||
Future<dynamic> getRequest({required String url, bool isByteResponse = false, bool isStreamReponse = false}) async {
|
||||
try {
|
||||
var dio = Dio();
|
||||
dio.interceptors.add(AuthenticatedRequestInterceptor());
|
||||
|
||||
var savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey);
|
||||
Response res = await dio.get('$savedEndpoint/$url');
|
||||
|
||||
if (res.statusCode == 200) {
|
||||
return res;
|
||||
if (isByteResponse) {
|
||||
Response<List<int>> res = await dio.get<List<int>>(
|
||||
'$savedEndpoint/$url',
|
||||
options: Options(responseType: ResponseType.bytes),
|
||||
);
|
||||
|
||||
if (res.statusCode == 200) {
|
||||
return res;
|
||||
}
|
||||
} else if (isStreamReponse) {
|
||||
Response<ResponseBody> res = await dio.get<ResponseBody>(
|
||||
'$savedEndpoint/$url',
|
||||
options: Options(responseType: ResponseType.stream),
|
||||
);
|
||||
|
||||
if (res.statusCode == 200) {
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
Response res = await dio.get('$savedEndpoint/$url');
|
||||
if (res.statusCode == 200) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
debugPrint("DioError: ${e.response}");
|
||||
|
||||
@@ -8,12 +8,24 @@ class ImmichToast {
|
||||
required BuildContext context,
|
||||
required String msg,
|
||||
ToastType toastType = ToastType.info,
|
||||
ToastGravity gravity = ToastGravity.TOP,
|
||||
}) {
|
||||
FToast fToast;
|
||||
|
||||
fToast = FToast();
|
||||
fToast.init(context);
|
||||
|
||||
_getColor(ToastType type, BuildContext context) {
|
||||
switch (type) {
|
||||
case ToastType.info:
|
||||
return Theme.of(context).primaryColor;
|
||||
case ToastType.success:
|
||||
return const Color.fromARGB(255, 78, 140, 124);
|
||||
case ToastType.error:
|
||||
return const Color.fromARGB(255, 220, 48, 85);
|
||||
}
|
||||
}
|
||||
|
||||
fToast.showToast(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
|
||||
@@ -36,8 +48,8 @@ class ImmichToast {
|
||||
: Container(),
|
||||
(toastType == ToastType.success)
|
||||
? const Icon(
|
||||
Icons.check,
|
||||
color: Color.fromARGB(255, 104, 248, 140),
|
||||
Icons.check_circle_rounded,
|
||||
color: Color.fromARGB(255, 78, 140, 124),
|
||||
)
|
||||
: Container(),
|
||||
(toastType == ToastType.error)
|
||||
@@ -53,7 +65,7 @@ class ImmichToast {
|
||||
child: Text(
|
||||
msg,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
color: _getColor(toastType, context),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15,
|
||||
),
|
||||
@@ -62,7 +74,7 @@ class ImmichToast {
|
||||
],
|
||||
),
|
||||
),
|
||||
gravity: ToastGravity.TOP,
|
||||
gravity: gravity,
|
||||
toastDuration: const Duration(seconds: 2),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
import 'package:chewie/chewie.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class VideoViewerPage extends StatelessWidget {
|
||||
final String videoUrl;
|
||||
|
||||
const VideoViewerPage({Key? key, required this.videoUrl}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String jwtToken = Hive.box(userInfoBox).get(accessTokenKey);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
systemOverlayStyle: SystemUiOverlayStyle.light,
|
||||
backgroundColor: Colors.black,
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
AutoRouter.of(context).pop();
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back_ios)),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: VideoThumbnailPlayer(
|
||||
url: videoUrl,
|
||||
jwtToken: jwtToken,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoThumbnailPlayer extends StatefulWidget {
|
||||
final String url;
|
||||
final String? jwtToken;
|
||||
|
||||
const VideoThumbnailPlayer({Key? key, required this.url, this.jwtToken}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<VideoThumbnailPlayer> createState() => _VideoThumbnailPlayerState();
|
||||
}
|
||||
|
||||
class _VideoThumbnailPlayerState extends State<VideoThumbnailPlayer> {
|
||||
late VideoPlayerController videoPlayerController;
|
||||
ChewieController? chewieController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initializePlayer();
|
||||
}
|
||||
|
||||
Future<void> initializePlayer() async {
|
||||
try {
|
||||
videoPlayerController =
|
||||
VideoPlayerController.network(widget.url, httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"});
|
||||
|
||||
await videoPlayerController.initialize();
|
||||
_createChewieController();
|
||||
setState(() {});
|
||||
} catch (e) {
|
||||
debugPrint("ERROR initialize video player");
|
||||
}
|
||||
}
|
||||
|
||||
_createChewieController() {
|
||||
chewieController = ChewieController(
|
||||
showOptions: true,
|
||||
showControlsOnInitialize: false,
|
||||
videoPlayerController: videoPlayerController,
|
||||
autoPlay: true,
|
||||
autoInitialize: false,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
videoPlayerController.pause();
|
||||
videoPlayerController.dispose();
|
||||
chewieController?.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return chewieController != null && chewieController!.videoPlayerController.value.isInitialized
|
||||
? SizedBox(
|
||||
child: Chewie(
|
||||
controller: chewieController!,
|
||||
),
|
||||
)
|
||||
: const SizedBox(
|
||||
width: 75,
|
||||
height: 75,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user