Skip to content

Commit

Permalink
feat(mobile): enhance download operations (#12973)
Browse files Browse the repository at this point in the history
* add packages

* create download task

* show progress

* save video and image

* show progress info

* live photo wip

* download and link live photos

* Update list of assets

* wip

* correct progress

* add state to download

* revert unncessary change

* repository pattern

* translation

* remove unused code

* update method call from repository

* remove unused variable

* handle multiple livephotos download

* remove logging statement

* lint

* not removing all records
  • Loading branch information
alextran1502 authored Sep 29, 2024
1 parent 2bcd27e commit fa9bb80
Show file tree
Hide file tree
Showing 20 changed files with 868 additions and 285 deletions.
15 changes: 13 additions & 2 deletions mobile/assets/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -588,5 +588,16 @@
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
"viewer_remove_from_stack": "Remove from Stack",
"viewer_stack_use_as_main_asset": "Use as Main Asset",
"viewer_unstack": "Un-Stack"
}
"viewer_unstack": "Un-Stack",
"downloading_media": "Downloading media",
"download_finished": "Download finished",
"download_filename": "file: {}",
"downloading": "Downloading...",
"download_complete": "Download complete",
"download_failed": "Download failed",
"download_canceled": "Download canceled",
"download_paused": "Download paused",
"download_enqueue": "Download enqueued",
"download_notfound": "Download not found",
"download_waiting_to_retry": "Waiting to retry"
}
6 changes: 6 additions & 0 deletions mobile/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
PODS:
- background_downloader (0.0.1):
- Flutter
- connectivity_plus (0.0.1):
- Flutter
- ReachabilitySwift
Expand Down Expand Up @@ -99,6 +101,7 @@ PODS:
- Flutter

DEPENDENCIES:
- background_downloader (from `.symlinks/plugins/background_downloader/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
Expand Down Expand Up @@ -137,6 +140,8 @@ SPEC REPOS:
- Toast

EXTERNAL SOURCES:
background_downloader:
:path: ".symlinks/plugins/background_downloader/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
device_info_plus:
Expand Down Expand Up @@ -189,6 +194,7 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock_plus/ios"

SPEC CHECKSUMS:
background_downloader: 9f788ffc5de45acf87d6380e91ca0841066c18cf
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
Expand Down
14 changes: 14 additions & 0 deletions mobile/lib/interfaces/download.interface.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:background_downloader/background_downloader.dart';

abstract interface class IDownloadRepository {
void Function(TaskStatusUpdate)? onImageDownloadStatus;
void Function(TaskStatusUpdate)? onVideoDownloadStatus;
void Function(TaskStatusUpdate)? onLivePhotoDownloadStatus;
void Function(TaskProgressUpdate)? onTaskProgress;

Future<List<TaskRecord>> getLiveVideoTasks();
Future<bool> download(DownloadTask task);
Future<bool> cancel(String id);
Future<void> deleteAllTrackingRecords();
Future<void> deleteRecordsWithIds(List<String> id);
}
25 changes: 22 additions & 3 deletions mobile/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';

import 'package:background_downloader/background_downloader.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
Expand All @@ -9,6 +10,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/utils/download.dart';
import 'package:timezone/data/latest.dart';
import 'package:immich_mobile/constants/locales.dart';
import 'package:immich_mobile/services/background.service.dart';
Expand Down Expand Up @@ -72,7 +74,6 @@ Future<void> initApp() async {
var log = Logger("ImmichErrorLogger");

FlutterError.onError = (details) {
debugPrint("FlutterError - Catch all: $details");
FlutterError.presentError(details);
log.severe(
'FlutterError - Catch all',
Expand All @@ -82,11 +83,29 @@ Future<void> initApp() async {
};

PlatformDispatcher.instance.onError = (error, stack) {
debugPrint("FlutterError - Catch all: $error");
log.severe('PlatformDispatcher - Catch all', error, stack);
return true;
};

initializeTimeZones();

FileDownloader().configureNotification(
running: TaskNotification(
'downloading_media'.tr(),
'file: {filename}',
),
complete: TaskNotification(
'download_finished'.tr(),
'file: {filename}',
),
progressBar: true,
);

FileDownloader().trackTasksInGroup(
downloadGroupLivePhoto,
markDownloadedComplete: false,
);
}

Future<Isar> loadDb() async {
Expand Down Expand Up @@ -188,8 +207,8 @@ class ImmichAppState extends ConsumerState<ImmichApp>

@override
Widget build(BuildContext context) {
var router = ref.watch(appRouterProvider);
var immichTheme = ref.watch(immichThemeProvider);
final router = ref.watch(appRouterProvider);
final immichTheme = ref.watch(immichThemeProvider);

return MaterialApp(
localizationsDelegates: context.localizationDelegates,
Expand Down
55 changes: 0 additions & 55 deletions mobile/lib/models/asset_viewer/asset_viewer_page_state.model.dart

This file was deleted.

109 changes: 109 additions & 0 deletions mobile/lib/models/download/download_state.model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert';

import 'package:background_downloader/background_downloader.dart';
import 'package:collection/collection.dart';

class DownloadInfo {
final String fileName;
final double progress;
// enum
final TaskStatus status;

DownloadInfo({
required this.fileName,
required this.progress,
required this.status,
});

DownloadInfo copyWith({
String? fileName,
double? progress,
TaskStatus? status,
}) {
return DownloadInfo(
fileName: fileName ?? this.fileName,
progress: progress ?? this.progress,
status: status ?? this.status,
);
}

Map<String, dynamic> toMap() {
return <String, dynamic>{
'fileName': fileName,
'progress': progress,
'status': status.index,
};
}

factory DownloadInfo.fromMap(Map<String, dynamic> map) {
return DownloadInfo(
fileName: map['fileName'] as String,
progress: map['progress'] as double,
status: TaskStatus.values[map['status'] as int],
);
}

String toJson() => json.encode(toMap());

factory DownloadInfo.fromJson(String source) =>
DownloadInfo.fromMap(json.decode(source) as Map<String, dynamic>);

@override
String toString() =>
'DownloadInfo(fileName: $fileName, progress: $progress, status: $status)';

@override
bool operator ==(covariant DownloadInfo other) {
if (identical(this, other)) return true;

return other.fileName == fileName &&
other.progress == progress &&
other.status == status;
}

@override
int get hashCode => fileName.hashCode ^ progress.hashCode ^ status.hashCode;
}

class DownloadState {
// enum
final TaskStatus downloadStatus;
final Map<String, DownloadInfo> taskProgress;
final bool showProgress;
DownloadState({
required this.downloadStatus,
required this.taskProgress,
required this.showProgress,
});

DownloadState copyWith({
TaskStatus? downloadStatus,
Map<String, DownloadInfo>? taskProgress,
bool? showProgress,
}) {
return DownloadState(
downloadStatus: downloadStatus ?? this.downloadStatus,
taskProgress: taskProgress ?? this.taskProgress,
showProgress: showProgress ?? this.showProgress,
);
}

@override
String toString() =>
'DownloadState(downloadStatus: $downloadStatus, taskProgress: $taskProgress, showProgress: $showProgress)';

@override
bool operator ==(covariant DownloadState other) {
if (identical(this, other)) return true;
final mapEquals = const DeepCollectionEquality().equals;

return other.downloadStatus == downloadStatus &&
mapEquals(other.taskProgress, taskProgress) &&
other.showProgress == showProgress;
}

@override
int get hashCode =>
downloadStatus.hashCode ^ taskProgress.hashCode ^ showProgress.hashCode;
}
60 changes: 60 additions & 0 deletions mobile/lib/models/download/livephotos_medatada.model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert';

enum LivePhotosPart {
video,
image,
}

class LivePhotosMetadata {
// enum
LivePhotosPart part;

String id;
LivePhotosMetadata({
required this.part,
required this.id,
});

LivePhotosMetadata copyWith({
LivePhotosPart? part,
String? id,
}) {
return LivePhotosMetadata(
part: part ?? this.part,
id: id ?? this.id,
);
}

Map<String, dynamic> toMap() {
return <String, dynamic>{
'part': part.index,
'id': id,
};
}

factory LivePhotosMetadata.fromMap(Map<String, dynamic> map) {
return LivePhotosMetadata(
part: LivePhotosPart.values[map['part'] as int],
id: map['id'] as String,
);
}

String toJson() => json.encode(toMap());

factory LivePhotosMetadata.fromJson(String source) =>
LivePhotosMetadata.fromMap(json.decode(source) as Map<String, dynamic>);

@override
String toString() => 'LivePhotosMetadata(part: $part, id: $id)';

@override
bool operator ==(covariant LivePhotosMetadata other) {
if (identical(this, other)) return true;

return other.part == part && other.id == id;
}

@override
int get hashCode => part.hashCode ^ id.hashCode;
}
Loading

0 comments on commit fa9bb80

Please sign in to comment.