mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	Implemented load new image when navigating back from backup page (#9)
This commit is contained in:
		
							
								
								
									
										14
									
								
								.github/workflows/Build+push Immich.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/Build+push Immich.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,13 +3,13 @@ name: Build+push Immich | ||||
| on: | ||||
|   # Triggers the workflow on push or pull request events but only for the main branch | ||||
|   #schedule: | ||||
|     # * is a special character in YAML so you have to quote this string | ||||
|     #- cron:  '0 0 * * *' | ||||
|   # * is a special character in YAML so you have to quote this string | ||||
|   #- cron:  '0 0 * * *' | ||||
|   workflow_dispatch: | ||||
|   #push: | ||||
|     #branches: [ main ] | ||||
|   pull_request: | ||||
|     branches: [ main ] | ||||
|   # push: | ||||
|   #branches: [ main ] | ||||
|   # pull_request: | ||||
|   #   branches: [ main ] | ||||
|  | ||||
| jobs: | ||||
|   buildandpush: | ||||
| @@ -18,7 +18,7 @@ jobs: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v2.4.0 | ||||
|         with: | ||||
|           ref: 'main' # branch | ||||
|           ref: "main" # branch | ||||
|       # https://github.com/docker/setup-qemu-action#usage | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v1.2.0 | ||||
|   | ||||
| @@ -79,7 +79,7 @@ flutter run --release | ||||
|  | ||||
| # Known Issue | ||||
|  | ||||
| TensorFlow doesn't run with older CPU architecture, it requires CPU with AVX and AVX2 instruction set. If you encounter error `illegal instruction core dump` when running the docker-compose command above, check for your CPU flags with the command ad make sure you see `AVX` and `AVX2`. Otherwise, switch to a different VM/desktop with different architecture. | ||||
| TensorFlow doesn't run with older CPU architecture, it requires CPU with AVX and AVX2 instruction set. If you encounter the error `illegal instruction core dump` when running the docker-compose command above, check for your CPU flags with the command and make sure you see `AVX` and `AVX2`. Otherwise, switch to a different VM/desktop with different architecture. | ||||
|  | ||||
| ```bash | ||||
| more /proc/cpuinfo | grep flags | ||||
|   | ||||
| @@ -1,15 +1,19 @@ | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/home/models/get_all_asset_respose.model.dart'; | ||||
| import 'package:immich_mobile/modules/home/services/asset.service.dart'; | ||||
| import 'package:immich_mobile/shared/models/immich_asset.model.dart'; | ||||
| import 'package:intl/intl.dart'; | ||||
| import 'package:collection/collection.dart'; | ||||
|  | ||||
| class AssetNotifier extends StateNotifier<List<ImmichAssetGroupByDate>> { | ||||
|   final imagePerPage = 100; | ||||
|   final AssetService _assetService = AssetService(); | ||||
|  | ||||
|   AssetNotifier() : super([]); | ||||
|  | ||||
|   late String? nextPageKey = ""; | ||||
|   bool isFetching = false; | ||||
|  | ||||
|   // Get All assets | ||||
|   getImmichAssets() async { | ||||
|     GetAllAssetResponse? res = await _assetService.getAllAsset(); | ||||
|     nextPageKey = res?.nextPageKey; | ||||
| @@ -21,10 +25,11 @@ class AssetNotifier extends StateNotifier<List<ImmichAssetGroupByDate>> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   getMoreAsset() async { | ||||
|   // Get Asset From The Past | ||||
|   getOlderAsset() async { | ||||
|     if (nextPageKey != null && !isFetching) { | ||||
|       isFetching = true; | ||||
|       GetAllAssetResponse? res = await _assetService.getMoreAsset(nextPageKey); | ||||
|       GetAllAssetResponse? res = await _assetService.getOlderAsset(nextPageKey); | ||||
|  | ||||
|       if (res != null) { | ||||
|         nextPageKey = res.nextPageKey; | ||||
| @@ -48,6 +53,40 @@ class AssetNotifier extends StateNotifier<List<ImmichAssetGroupByDate>> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Get newer asset from the current time | ||||
|   getNewAsset() async { | ||||
|     if (state.isNotEmpty) { | ||||
|       var latestGroup = state.first; | ||||
|  | ||||
|       // Sort the last asset group and put the lastest asset in front. | ||||
|       latestGroup.assets.sortByCompare<DateTime>((e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a)); | ||||
|       var latestAsset = latestGroup.assets.first; | ||||
|       var formatDateTemplate = 'y-MM-dd'; | ||||
|       var latestAssetDateText = DateFormat(formatDateTemplate).format(DateTime.parse(latestAsset.createdAt)); | ||||
|  | ||||
|       List<ImmichAsset> newAssets = await _assetService.getNewAsset(latestAsset.createdAt); | ||||
|  | ||||
|       if (newAssets.isEmpty) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       // Grouping by data | ||||
|       var groupByDateList = groupBy<ImmichAsset, String>( | ||||
|           newAssets, (asset) => DateFormat(formatDateTemplate).format(DateTime.parse(asset.createdAt))); | ||||
|  | ||||
|       groupByDateList.forEach((groupDateInFormattedText, assets) { | ||||
|         if (groupDateInFormattedText != latestAssetDateText) { | ||||
|           ImmichAssetGroupByDate newGroup = ImmichAssetGroupByDate(assets: assets, date: groupDateInFormattedText); | ||||
|           state = [newGroup, ...state]; | ||||
|         } else { | ||||
|           latestGroup.assets.insertAll(0, assets); | ||||
|  | ||||
|           state = [latestGroup, ...state.sublist(1)]; | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   clearAllAsset() { | ||||
|     state = []; | ||||
|   } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import 'dart:convert'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:immich_mobile/modules/home/models/get_all_asset_respose.model.dart'; | ||||
| import 'package:immich_mobile/shared/models/immich_asset.model.dart'; | ||||
| import 'package:immich_mobile/shared/services/network.service.dart'; | ||||
|  | ||||
| class AssetService { | ||||
| @@ -17,9 +18,10 @@ class AssetService { | ||||
|     } catch (e) { | ||||
|       debugPrint("Error getAllAsset  ${e.toString()}"); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   Future<GetAllAssetResponse?> getMoreAsset(String? nextPageKey) async { | ||||
|   Future<GetAllAssetResponse?> getOlderAsset(String? nextPageKey) async { | ||||
|     try { | ||||
|       var res = await _networkService.getRequest( | ||||
|         url: "asset/all?nextPageKey=$nextPageKey", | ||||
| @@ -34,5 +36,26 @@ class AssetService { | ||||
|     } catch (e) { | ||||
|       debugPrint("Error getAllAsset  ${e.toString()}"); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   Future<List<ImmichAsset>> getNewAsset(String latestDate) async { | ||||
|     try { | ||||
|       var res = await _networkService.getRequest( | ||||
|         url: "asset/new?latestDate=$latestDate", | ||||
|       ); | ||||
|  | ||||
|       List<dynamic> decodedData = jsonDecode(res.toString()); | ||||
|  | ||||
|       List<ImmichAsset> result = List.from(decodedData.map((a) => ImmichAsset.fromMap(a))); | ||||
|       if (result.isNotEmpty) { | ||||
|         return result; | ||||
|       } | ||||
|  | ||||
|       return []; | ||||
|     } catch (e) { | ||||
|       debugPrint("Error getAllAsset  ${e.toString()}"); | ||||
|       return []; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,6 @@ import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:google_fonts/google_fonts.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/home/providers/asset.provider.dart'; | ||||
|  | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/shared/models/backup_state.model.dart'; | ||||
| @@ -12,9 +11,11 @@ class ImmichSliverAppBar extends ConsumerWidget { | ||||
|   const ImmichSliverAppBar({ | ||||
|     Key? key, | ||||
|     required this.imageGridGroup, | ||||
|     this.onPopBack, | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   final List<Widget> imageGridGroup; | ||||
|   final Function? onPopBack; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
| @@ -75,15 +76,7 @@ class ImmichSliverAppBar extends ConsumerWidget { | ||||
|                   var onPop = await AutoRouter.of(context).push(const BackupControllerRoute()); | ||||
|  | ||||
|                   if (onPop == true) { | ||||
|                     // Remove and force getting new widget again if there is not many widget on screen. | ||||
|                     // Otherwise do nothing. | ||||
|                     if (imageGridGroup.isNotEmpty && imageGridGroup.length < 20) { | ||||
|                       print("Get more access"); | ||||
|                       ref.read(assetProvider.notifier).getMoreAsset(); | ||||
|                     } else if (imageGridGroup.isEmpty) { | ||||
|                       print("get immich asset"); | ||||
|                       ref.read(assetProvider.notifier).getImmichAssets(); | ||||
|                     } | ||||
|                     onPopBack!(); | ||||
|                   } | ||||
|                 }, | ||||
|               ), | ||||
|   | ||||
| @@ -1,12 +1,9 @@ | ||||
| import 'package:auto_route/annotations.dart'; | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/src/widgets/framework.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/home/providers/asset.provider.dart'; | ||||
| import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; | ||||
| import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
|  | ||||
| class ProfileDrawer extends ConsumerWidget { | ||||
|   const ProfileDrawer({Key? key}) : super(key: key); | ||||
| @@ -58,6 +55,7 @@ class ProfileDrawer extends ConsumerWidget { | ||||
|             ), | ||||
|             onTap: () async { | ||||
|               bool res = await ref.read(authenticationProvider.notifier).logout(); | ||||
|  | ||||
|               ref.read(assetProvider.notifier).clearAllAsset(); | ||||
|  | ||||
|               if (res) { | ||||
|   | ||||
| @@ -24,7 +24,7 @@ class HomePage extends HookConsumerWidget { | ||||
|       var endOfPage = _scrollController.position.maxScrollExtent; | ||||
|  | ||||
|       if (_scrollController.offset >= endOfPage - (endOfPage * 0.1) && !_scrollController.position.outOfRange) { | ||||
|         ref.read(assetProvider.notifier).getMoreAsset(); | ||||
|         ref.read(assetProvider.notifier).getOlderAsset(); | ||||
|       } | ||||
|  | ||||
|       if (_scrollController.offset >= 400) { | ||||
| @@ -44,6 +44,18 @@ class HomePage extends HookConsumerWidget { | ||||
|       }; | ||||
|     }, []); | ||||
|  | ||||
|     onPopBackFromBackupPage() { | ||||
|       ref.read(assetProvider.notifier).getNewAsset(); | ||||
|       // Remove and force getting new widget again if there is not many widget on screen. | ||||
|       // Otherwise do nothing. | ||||
|  | ||||
|       if (imageGridGroup.isNotEmpty && imageGridGroup.length < 20) { | ||||
|         ref.read(assetProvider.notifier).getOlderAsset(); | ||||
|       } else if (imageGridGroup.isEmpty) { | ||||
|         ref.read(assetProvider.notifier).getImmichAssets(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Widget _buildBody() { | ||||
|       if (assetGroup.isNotEmpty) { | ||||
|         String lastGroupDate = assetGroup[0].date; | ||||
| @@ -56,10 +68,13 @@ class HomePage extends HookConsumerWidget { | ||||
|           int? previousMonth = DateTime.tryParse(lastGroupDate)?.month; | ||||
|  | ||||
|           // Add Monthly Title Group if started at the beginning of the month | ||||
|           if ((currentMonth! - previousMonth!) != 0) { | ||||
|             imageGridGroup.add( | ||||
|               MonthlyTitleText(isoDate: dateTitle), | ||||
|             ); | ||||
|  | ||||
|           if (currentMonth != null && previousMonth != null) { | ||||
|             if ((currentMonth - previousMonth) != 0) { | ||||
|               imageGridGroup.add( | ||||
|                 MonthlyTitleText(isoDate: dateTitle), | ||||
|               ); | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           // Add Daily Title Group | ||||
| @@ -84,7 +99,10 @@ class HomePage extends HookConsumerWidget { | ||||
|           child: CustomScrollView( | ||||
|             controller: _scrollController, | ||||
|             slivers: [ | ||||
|               ImmichSliverAppBar(imageGridGroup: imageGridGroup), | ||||
|               ImmichSliverAppBar( | ||||
|                 imageGridGroup: imageGridGroup, | ||||
|                 onPopBack: onPopBackFromBackupPage, | ||||
|               ), | ||||
|               ...imageGridGroup, | ||||
|             ], | ||||
|           ), | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| 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'; | ||||
| @@ -17,6 +18,7 @@ class VideoViewerPage extends StatelessWidget { | ||||
|     return Scaffold( | ||||
|       backgroundColor: Colors.black, | ||||
|       appBar: AppBar( | ||||
|         systemOverlayStyle: SystemUiOverlayStyle.light, | ||||
|         backgroundColor: Colors.black, | ||||
|         leading: IconButton( | ||||
|             onPressed: () { | ||||
| @@ -24,7 +26,7 @@ class VideoViewerPage extends StatelessWidget { | ||||
|             }, | ||||
|             icon: const Icon(Icons.arrow_back_ios)), | ||||
|       ), | ||||
|       body: Center( | ||||
|       body: SafeArea( | ||||
|         child: VideoThumbnailPlayer( | ||||
|           url: videoUrl, | ||||
|           jwtToken: jwtToken, | ||||
| @@ -64,7 +66,6 @@ class _VideoThumbnailPlayerState extends State<VideoThumbnailPlayer> { | ||||
|       setState(() {}); | ||||
|     } catch (e) { | ||||
|       debugPrint("ERROR initialize video player"); | ||||
|       print(e); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -29,6 +29,7 @@ import { Response as Res } from 'express'; | ||||
| import { promisify } from 'util'; | ||||
| import { stat } from 'fs'; | ||||
| import { pipeline } from 'stream'; | ||||
| import { GetNewAssetQueryDto } from './dto/get-new-asset-query.dto'; | ||||
|  | ||||
| const fileInfo = promisify(stat); | ||||
|  | ||||
| @@ -141,6 +142,11 @@ export class AssetController { | ||||
|     console.log('SHOULD NOT BE HERE'); | ||||
|   } | ||||
|  | ||||
|   @Get('/new') | ||||
|   async getNewAssets(@GetAuthUser() authUser: AuthUserDto, @Query(ValidationPipe) query: GetNewAssetQueryDto) { | ||||
|     return await this.assetService.getNewAssets(authUser, query.latestDate); | ||||
|   } | ||||
|  | ||||
|   @Get('/all') | ||||
|   async getAllAssets(@GetAuthUser() authUser: AuthUserDto, @Query(ValidationPipe) query: GetAllAssetQueryDto) { | ||||
|     return await this.assetService.getAllAssets(authUser, query); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { BadRequestException, Injectable, Logger } from '@nestjs/common'; | ||||
| import { InjectRepository } from '@nestjs/typeorm'; | ||||
| import { Repository } from 'typeorm'; | ||||
| import { MoreThan, Repository } from 'typeorm'; | ||||
| import { AuthUserDto } from '../../decorators/auth-user.decorator'; | ||||
| import { CreateAssetDto } from './dto/create-asset.dto'; | ||||
| import { UpdateAssetDto } from './dto/update-asset.dto'; | ||||
| @@ -8,6 +8,7 @@ import { AssetEntity, AssetType } from './entities/asset.entity'; | ||||
| import _ from 'lodash'; | ||||
| import { GetAllAssetQueryDto } from './dto/get-all-asset-query.dto'; | ||||
| import { GetAllAssetReponseDto } from './dto/get-all-asset-response.dto'; | ||||
| import { Greater } from '@tensorflow/tfjs-core'; | ||||
|  | ||||
| @Injectable() | ||||
| export class AssetService { | ||||
| @@ -53,8 +54,6 @@ export class AssetService { | ||||
|   } | ||||
|  | ||||
|   public async getAllAssets(authUser: AuthUserDto, query: GetAllAssetQueryDto): Promise<GetAllAssetReponseDto> { | ||||
|     // Each page will take 100 images. | ||||
|  | ||||
|     try { | ||||
|       const assets = await this.assetRepository | ||||
|         .createQueryBuilder('a') | ||||
| @@ -63,7 +62,7 @@ export class AssetService { | ||||
|           lastQueryCreatedAt: query.nextPageKey || new Date().toISOString(), | ||||
|         }) | ||||
|         .orderBy('a."createdAt"::date', 'DESC') | ||||
|         // .take(500) | ||||
|         .take(5000) | ||||
|         .getMany(); | ||||
|  | ||||
|       if (assets.length > 0) { | ||||
| @@ -102,4 +101,16 @@ export class AssetService { | ||||
|  | ||||
|     return rows[0] as AssetEntity; | ||||
|   } | ||||
|  | ||||
|   public async getNewAssets(authUser: AuthUserDto, latestDate: string) { | ||||
|     return await this.assetRepository.find({ | ||||
|       where: { | ||||
|         userId: authUser.id, | ||||
|         createdAt: MoreThan(latestDate), | ||||
|       }, | ||||
|       order: { | ||||
|         createdAt: 'ASC', // ASC order to add existed asset the latest group first before creating a new date group. | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { IsNotEmpty } from 'class-validator'; | ||||
|  | ||||
| class GetAssetDto { | ||||
| export class GetAssetDto { | ||||
|   @IsNotEmpty() | ||||
|   deviceId: string; | ||||
| } | ||||
|   | ||||
							
								
								
									
										6
									
								
								server/src/api-v1/asset/dto/get-new-asset-query.dto.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								server/src/api-v1/asset/dto/get-new-asset-query.dto.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| import { IsNotEmpty } from 'class-validator'; | ||||
|  | ||||
| export class GetNewAssetQueryDto { | ||||
|   @IsNotEmpty() | ||||
|   latestDate: string; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user