Skip to content

Commit

Permalink
feat(mobile): show local assets (immich-app#905)
Browse files Browse the repository at this point in the history
* introduce Asset as composition of AssetResponseDTO and AssetEntity

* filter out duplicate assets (that are both local and remote, take only remote for now)

* only allow remote images to be added to albums

* introduce ImmichImage to render Asset using local or remote data

* optimized deletion of local assets

* local video file playback

* allow multiple methods to wait on background service finished

* skip local assets when adding to album from home screen

* fix and optimize delete

* show gray box placeholder for local assets

* add comments

* fix bug: duplicate assets in state after onNewAssetUploaded
  • Loading branch information
Fynn Petersen-Frey authored Nov 8, 2022
1 parent 99da181 commit 1633af7
Show file tree
Hide file tree
Showing 41 changed files with 823 additions and 507 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,13 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
}

private fun stopEngine(result: Result?) {
clearBackgroundNotification()
engine?.destroy()
engine = null
if (result != null) {
Log.d(TAG, "stopEngine result=${result}")
resolvableFuture.set(result)
}
engine?.destroy()
engine = null
clearBackgroundNotification()
waitOnSetForegroundAsync()
}

Expand Down
8 changes: 5 additions & 3 deletions mobile/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ void main() async {
await Future.wait([
Hive.openBox(userInfoBox),
Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox),
Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox),
Hive.openBox(hiveGithubReleaseInfoBox),
Hive.openBox(userSettingInfoBox),
Hive.openBox<HiveDuplicatedAssets>(duplicatedAssetsBox),
if (!Platform.isAndroid) Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox),
if (!Platform.isAndroid)
Hive.openBox<HiveDuplicatedAssets>(duplicatedAssetsBox),
if (!Platform.isAndroid) Hive.openBox(backgroundBackupInfoBox),
EasyLocalization.ensureInitialized(),
]);

Expand Down Expand Up @@ -86,8 +88,8 @@ class ImmichAppState extends ConsumerState<ImmichApp>
var isAuthenticated = ref.watch(authenticationProvider).isAuthenticated;

if (isAuthenticated) {
ref.read(backupProvider.notifier).resumeBackup();
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
ref.watch(backupProvider.notifier).resumeBackup();
ref.watch(assetProvider.notifier).getAllAsset();
ref.watch(serverInfoProvider.notifier).getServerVersion();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import 'package:collection/collection.dart';

import 'package:openapi/api.dart';
import 'package:immich_mobile/shared/models/asset.dart';

class AssetSelectionPageResult {
final Set<AssetResponseDto> selectedNewAsset;
final Set<AssetResponseDto> selectedAdditionalAsset;
final Set<Asset> selectedNewAsset;
final Set<Asset> selectedAdditionalAsset;
final bool isAlbumExist;

AssetSelectionPageResult({
Expand All @@ -14,8 +13,8 @@ class AssetSelectionPageResult {
});

AssetSelectionPageResult copyWith({
Set<AssetResponseDto>? selectedNewAsset,
Set<AssetResponseDto>? selectedAdditionalAsset,
Set<Asset>? selectedNewAsset,
Set<Asset>? selectedAdditionalAsset,
bool? isAlbumExist,
}) {
return AssetSelectionPageResult(
Expand Down
15 changes: 7 additions & 8 deletions mobile/lib/modules/album/models/asset_selection_state.model.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import 'package:collection/collection.dart';

import 'package:openapi/api.dart';
import 'package:immich_mobile/shared/models/asset.dart';

class AssetSelectionState {
final Set<String> selectedMonths;
final Set<AssetResponseDto> selectedNewAssetsForAlbum;
final Set<AssetResponseDto> selectedAdditionalAssetsForAlbum;
final Set<AssetResponseDto> selectedAssetsInAlbumViewer;
final Set<Asset> selectedNewAssetsForAlbum;
final Set<Asset> selectedAdditionalAssetsForAlbum;
final Set<Asset> selectedAssetsInAlbumViewer;
final bool isMultiselectEnable;

/// Indicate the asset selection page is navigated from existing album
Expand All @@ -22,9 +21,9 @@ class AssetSelectionState {

AssetSelectionState copyWith({
Set<String>? selectedMonths,
Set<AssetResponseDto>? selectedNewAssetsForAlbum,
Set<AssetResponseDto>? selectedAdditionalAssetsForAlbum,
Set<AssetResponseDto>? selectedAssetsInAlbumViewer,
Set<Asset>? selectedNewAssetsForAlbum,
Set<Asset>? selectedAdditionalAssetsForAlbum,
Set<Asset>? selectedAssetsInAlbumViewer,
bool? isMultiselectEnable,
bool? isAlbumExist,
}) {
Expand Down
4 changes: 2 additions & 2 deletions mobile/lib/modules/album/providers/album.provider.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/album/services/album_cache.service.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:openapi/api.dart';

class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
Expand All @@ -13,7 +14,6 @@ class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
}

getAllAlbums() async {

if (await _albumCacheService.isValid() && state.isEmpty) {
state = await _albumCacheService.get();
}
Expand All @@ -34,7 +34,7 @@ class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {

Future<AlbumResponseDto?> createAlbum(
String albumTitle,
Set<AssetResponseDto> assets,
Set<Asset> assets,
) async {
AlbumResponseDto? album =
await _albumService.createAlbum(albumTitle, assets, []);
Expand Down
35 changes: 17 additions & 18 deletions mobile/lib/modules/album/providers/asset_selection.provider.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_state.model.dart';

import 'package:openapi/api.dart';
import 'package:immich_mobile/shared/models/asset.dart';

class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
AssetSelectionNotifier()
Expand All @@ -22,15 +21,15 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {

void removeAssetsInMonth(
String removedMonth,
List<AssetResponseDto> assetsInMonth,
List<Asset> assetsInMonth,
) {
Set<AssetResponseDto> currentAssetList = state.selectedNewAssetsForAlbum;
Set<Asset> currentAssetList = state.selectedNewAssetsForAlbum;
Set<String> currentMonthList = state.selectedMonths;

currentMonthList
.removeWhere((selectedMonth) => selectedMonth == removedMonth);

for (AssetResponseDto asset in assetsInMonth) {
for (Asset asset in assetsInMonth) {
currentAssetList.removeWhere((e) => e.id == asset.id);
}

Expand All @@ -40,7 +39,7 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
);
}

void addAdditionalAssets(List<AssetResponseDto> assets) {
void addAdditionalAssets(List<Asset> assets) {
state = state.copyWith(
selectedAdditionalAssetsForAlbum: {
...state.selectedAdditionalAssetsForAlbum,
Expand All @@ -49,7 +48,7 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
);
}

void addAllAssetsInMonth(String month, List<AssetResponseDto> assetsInMonth) {
void addAllAssetsInMonth(String month, List<Asset> assetsInMonth) {
state = state.copyWith(
selectedMonths: {...state.selectedMonths, month},
selectedNewAssetsForAlbum: {
Expand All @@ -59,7 +58,7 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
);
}

void addNewAssets(List<AssetResponseDto> assets) {
void addNewAssets(List<Asset> assets) {
state = state.copyWith(
selectedNewAssetsForAlbum: {
...state.selectedNewAssetsForAlbum,
Expand All @@ -68,20 +67,20 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
);
}

void removeSelectedNewAssets(List<AssetResponseDto> assets) {
Set<AssetResponseDto> currentList = state.selectedNewAssetsForAlbum;
void removeSelectedNewAssets(List<Asset> assets) {
Set<Asset> currentList = state.selectedNewAssetsForAlbum;

for (AssetResponseDto asset in assets) {
for (Asset asset in assets) {
currentList.removeWhere((e) => e.id == asset.id);
}

state = state.copyWith(selectedNewAssetsForAlbum: currentList);
}

void removeSelectedAdditionalAssets(List<AssetResponseDto> assets) {
Set<AssetResponseDto> currentList = state.selectedAdditionalAssetsForAlbum;
void removeSelectedAdditionalAssets(List<Asset> assets) {
Set<Asset> currentList = state.selectedAdditionalAssetsForAlbum;

for (AssetResponseDto asset in assets) {
for (Asset asset in assets) {
currentList.removeWhere((e) => e.id == asset.id);
}

Expand Down Expand Up @@ -109,7 +108,7 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
);
}

void addAssetsInAlbumViewer(List<AssetResponseDto> assets) {
void addAssetsInAlbumViewer(List<Asset> assets) {
state = state.copyWith(
selectedAssetsInAlbumViewer: {
...state.selectedAssetsInAlbumViewer,
Expand All @@ -118,10 +117,10 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
);
}

void removeAssetsInAlbumViewer(List<AssetResponseDto> assets) {
Set<AssetResponseDto> currentList = state.selectedAssetsInAlbumViewer;
void removeAssetsInAlbumViewer(List<Asset> assets) {
Set<Asset> currentList = state.selectedAssetsInAlbumViewer;

for (AssetResponseDto asset in assets) {
for (Asset asset in assets) {
currentList.removeWhere((e) => e.id == asset.id);
}

Expand Down
6 changes: 4 additions & 2 deletions mobile/lib/modules/album/providers/shared_album.provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/album/services/album_cache.service.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:openapi/api.dart';

class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
SharedAlbumNotifier(this._sharedAlbumService, this._sharedAlbumCacheService) : super([]);
SharedAlbumNotifier(this._sharedAlbumService, this._sharedAlbumCacheService)
: super([]);

final AlbumService _sharedAlbumService;
final SharedAlbumCacheService _sharedAlbumCacheService;
Expand All @@ -16,7 +18,7 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {

Future<AlbumResponseDto?> createSharedAlbum(
String albumName,
Set<AssetResponseDto> assets,
Set<Asset> assets,
List<String> sharedUserIds,
) async {
try {
Expand Down
7 changes: 4 additions & 3 deletions mobile/lib/modules/album/services/album.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:openapi/api.dart';
Expand Down Expand Up @@ -29,7 +30,7 @@ class AlbumService {

Future<AlbumResponseDto?> createAlbum(
String albumName,
Set<AssetResponseDto> assets,
Iterable<Asset> assets,
List<String> sharedUserIds,
) async {
try {
Expand Down Expand Up @@ -65,7 +66,7 @@ class AlbumService {
}

Future<AlbumResponseDto?> createAlbumWithGeneratedName(
Set<AssetResponseDto> assets,
Iterable<Asset> assets,
) async {
return createAlbum(
_getNextAlbumName(await getAlbums(isShared: false)), assets, []);
Expand All @@ -81,7 +82,7 @@ class AlbumService {
}

Future<AddAssetsResponseDto?> addAdditionalAssetToAlbum(
Set<AssetResponseDto> assets,
Iterable<Asset> assets,
String albumId,
) async {
try {
Expand Down
37 changes: 6 additions & 31 deletions mobile/lib/modules/album/ui/album_viewer_thumbnail.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import 'package:auto_route/auto_route.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:openapi/api.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';

class AlbumViewerThumbnail extends HookConsumerWidget {
final AssetResponseDto asset;
final List<AssetResponseDto> assetList;
final Asset asset;
final List<Asset> assetList;
final bool showStorageIndicator;

const AlbumViewerThumbnail({
Expand All @@ -24,8 +21,6 @@ class AlbumViewerThumbnail extends HookConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
var box = Hive.box(userInfoBox);
var thumbnailRequestUrl = getThumbnailUrl(asset);
var deviceId = ref.watch(authenticationProvider).deviceId;
final selectedAssetsInAlbumViewer =
ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer;
Expand Down Expand Up @@ -120,27 +115,7 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
_buildThumbnailImage() {
return Container(
decoration: BoxDecoration(border: drawBorderColor()),
child: CachedNetworkImage(
cacheKey: asset.id,
width: 300,
height: 300,
memCacheHeight: 200,
fit: BoxFit.cover,
imageUrl: thumbnailRequestUrl,
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
fadeInDuration: const Duration(milliseconds: 250),
progressIndicatorBuilder: (context, url, downloadProgress) =>
Transform.scale(
scale: 0.2,
child: CircularProgressIndicator(value: downloadProgress.progress),
),
errorWidget: (context, url, error) {
return Icon(
Icons.image_not_supported_outlined,
color: Theme.of(context).primaryColor,
);
},
),
child: ImmichImage(asset, width: 300, height: 300),
);
}

Expand All @@ -167,7 +142,7 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
children: [
_buildThumbnailImage(),
if (showStorageIndicator) _buildAssetStoreLocationIcon(),
if (asset.type != AssetTypeEnum.IMAGE) _buildVideoLabel(),
if (!asset.isImage) _buildVideoLabel(),
if (isMultiSelectionEnable) _buildAssetSelectionIcon(),
],
),
Expand Down
4 changes: 2 additions & 2 deletions mobile/lib/modules/album/ui/asset_grid_by_month.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/ui/selection_thumbnail_image.dart';
import 'package:openapi/api.dart';
import 'package:immich_mobile/shared/models/asset.dart';

class AssetGridByMonth extends HookConsumerWidget {
final List<AssetResponseDto> assetGroup;
final List<Asset> assetGroup;
const AssetGridByMonth({Key? key, required this.assetGroup})
: super(key: key);
@override
Expand Down
4 changes: 2 additions & 2 deletions mobile/lib/modules/album/ui/month_group_title.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:openapi/api.dart';
import 'package:immich_mobile/shared/models/asset.dart';

class MonthGroupTitle extends HookConsumerWidget {
final String month;
final List<AssetResponseDto> assetGroup;
final List<Asset> assetGroup;

const MonthGroupTitle({
Key? key,
Expand Down
Loading

0 comments on commit 1633af7

Please sign in to comment.