You've already forked immich
							
							
				mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 00:18:28 +02:00 
			
		
		
		
	Optimize android's Gradle settings and clean up mobile source code (#240)
* optimize android side gradle settings * android minsdk back to 21 * remove unused package, update linter and fix lint error
This commit is contained in:
		| @@ -1,10 +1,45 @@ | ||||
| # This file tracks properties of this Flutter project. | ||||
| # Used by Flutter tool to assess capabilities and perform upgrades etc. | ||||
| # | ||||
| # This file should be version controlled and should not be manually edited. | ||||
| # This file should be version controlled. | ||||
|  | ||||
| version: | ||||
|   revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b | ||||
|   revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|   channel: stable | ||||
|  | ||||
| project_type: app | ||||
|  | ||||
| # Tracks metadata for the flutter migrate command | ||||
| migration: | ||||
|   platforms: | ||||
|     - platform: root | ||||
|       create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|       base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|     - platform: android | ||||
|       create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|       base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|     - platform: ios | ||||
|       create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|       base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|     - platform: linux | ||||
|       create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|       base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|     - platform: macos | ||||
|       create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|       base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|     - platform: web | ||||
|       create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|       base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|     - platform: windows | ||||
|       create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|       base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 | ||||
|  | ||||
|   # User provided section | ||||
|  | ||||
|   # List of Local paths (relative to this file) that should be | ||||
|   # ignored by the migrate tool. | ||||
|   # | ||||
|   # Files that are not part of the templates will be ignored by default. | ||||
|   unmanaged_files: | ||||
|     - 'lib/main.dart' | ||||
|     - 'ios/Runner.xcodeproj/project.pbxproj' | ||||
|   | ||||
| @@ -24,6 +24,7 @@ linter: | ||||
|   rules: | ||||
|     # avoid_print: false  # Uncomment to disable the `avoid_print` rule | ||||
|     # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule | ||||
|     use_build_context_synchronously: false | ||||
|  | ||||
| # Additional information about this file can be found at | ||||
| # https://dart.dev/guides/language/analysis-options | ||||
|   | ||||
| @@ -81,5 +81,4 @@ flutter { | ||||
|  | ||||
| dependencies { | ||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | ||||
|     implementation 'com.android.support:multidex:1.0.3' | ||||
| } | ||||
|   | ||||
| @@ -1,25 +0,0 @@ | ||||
| // Generated file. | ||||
| // | ||||
| // If you wish to remove Flutter's multidex support, delete this entire file. | ||||
| // | ||||
| // Modifications to this file should be done in a copy under a different name | ||||
| // as this file may be regenerated. | ||||
|  | ||||
| package io.flutter.app; | ||||
|  | ||||
| import android.app.Application; | ||||
| import android.content.Context; | ||||
| import androidx.annotation.CallSuper; | ||||
| import androidx.multidex.MultiDex; | ||||
|  | ||||
| /** | ||||
|  * Extension of {@link android.app.Application}, adding multidex support. | ||||
|  */ | ||||
| public class FlutterMultiDexApplication extends Application { | ||||
|   @Override | ||||
|   @CallSuper | ||||
|   protected void attachBaseContext(Context base) { | ||||
|     super.attachBaseContext(base); | ||||
|     MultiDex.install(this); | ||||
|   } | ||||
| } | ||||
| @@ -6,7 +6,7 @@ buildscript { | ||||
|     } | ||||
|  | ||||
|     dependencies { | ||||
|         classpath 'com.android.tools.build:gradle:4.1.0' | ||||
|         classpath 'com.android.tools.build:gradle:7.1.2' | ||||
|         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip | ||||
| distributionSha256Sum=0080de8491f0918e4f529a6db6820fa0b9e818ee2386117f4394f95feb1d5583 | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip | ||||
| distributionSha256Sum=cd5c2958a107ee7f0722004a12d0f8559b4564c34daad7df06cffd4d12a426d0 | ||||
| @@ -42,10 +42,11 @@ class ImmichApp extends ConsumerStatefulWidget { | ||||
|   const ImmichApp({Key? key}) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   _ImmichAppState createState() => _ImmichAppState(); | ||||
|   ImmichAppState createState() => ImmichAppState(); | ||||
| } | ||||
|  | ||||
| class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserver { | ||||
| class ImmichAppState extends ConsumerState<ImmichApp> | ||||
|     with WidgetsBindingObserver { | ||||
|   @override | ||||
|   void didChangeAppLifecycleState(AppLifecycleState state) { | ||||
|     switch (state) { | ||||
| @@ -121,7 +122,8 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv | ||||
|               brightness: Brightness.light, | ||||
|               primarySwatch: Colors.indigo, | ||||
|               fontFamily: 'WorkSans', | ||||
|               snackBarTheme: const SnackBarThemeData(contentTextStyle: TextStyle(fontFamily: 'WorkSans')), | ||||
|               snackBarTheme: const SnackBarThemeData( | ||||
|                   contentTextStyle: TextStyle(fontFamily: 'WorkSans')), | ||||
|               scaffoldBackgroundColor: immichBackgroundColor, | ||||
|               appBarTheme: const AppBarTheme( | ||||
|                 backgroundColor: immichBackgroundColor, | ||||
| @@ -132,7 +134,8 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv | ||||
|               ), | ||||
|             ), | ||||
|             routeInformationParser: _immichRouter.defaultRouteParser(), | ||||
|             routerDelegate: _immichRouter.delegate(navigatorObservers: () => [TabNavigationObserver(ref: ref)]), | ||||
|             routerDelegate: _immichRouter.delegate( | ||||
|                 navigatorObservers: () => [TabNavigationObserver(ref: ref)]), | ||||
|           ), | ||||
|           const ImmichLoadingOverlay(), | ||||
|           const VersionAnnouncementOverlay(), | ||||
|   | ||||
| @@ -9,12 +9,14 @@ import 'package:latlong2/latlong.dart'; | ||||
| class ExifBottomSheet extends ConsumerWidget { | ||||
|   final ImmichAssetWithExif assetDetail; | ||||
|  | ||||
|   const ExifBottomSheet({Key? key, required this.assetDetail}) : super(key: key); | ||||
|   const ExifBottomSheet({Key? key, required this.assetDetail}) | ||||
|       : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     _buildMap() { | ||||
|       return (assetDetail.exifInfo!.latitude != null && assetDetail.exifInfo!.longitude != null) | ||||
|       return (assetDetail.exifInfo!.latitude != null && | ||||
|               assetDetail.exifInfo!.longitude != null) | ||||
|           ? Padding( | ||||
|               padding: const EdgeInsets.symmetric(vertical: 16.0), | ||||
|               child: Container( | ||||
| @@ -25,12 +27,14 @@ class ExifBottomSheet extends ConsumerWidget { | ||||
|                 ), | ||||
|                 child: FlutterMap( | ||||
|                   options: MapOptions( | ||||
|                     center: LatLng(assetDetail.exifInfo!.latitude!, assetDetail.exifInfo!.longitude!), | ||||
|                     center: LatLng(assetDetail.exifInfo!.latitude!, | ||||
|                         assetDetail.exifInfo!.longitude!), | ||||
|                     zoom: 16.0, | ||||
|                   ), | ||||
|                   layers: [ | ||||
|                     TileLayerOptions( | ||||
|                       urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", | ||||
|                       urlTemplate: | ||||
|                           "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", | ||||
|                       subdomains: ['a', 'b', 'c'], | ||||
|                       attributionBuilder: (_) { | ||||
|                         return const Text( | ||||
| @@ -43,8 +47,10 @@ class ExifBottomSheet extends ConsumerWidget { | ||||
|                       markers: [ | ||||
|                         Marker( | ||||
|                           anchorPos: AnchorPos.align(AnchorAlign.top), | ||||
|                           point: LatLng(assetDetail.exifInfo!.latitude!, assetDetail.exifInfo!.longitude!), | ||||
|                           builder: (ctx) => const Image(image: AssetImage('assets/location-pin.png')), | ||||
|                           point: LatLng(assetDetail.exifInfo!.latitude!, | ||||
|                               assetDetail.exifInfo!.longitude!), | ||||
|                           builder: (ctx) => const Image( | ||||
|                               image: AssetImage('assets/location-pin.png')), | ||||
|                         ), | ||||
|                       ], | ||||
|                     ), | ||||
| @@ -56,10 +62,14 @@ class ExifBottomSheet extends ConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     _buildLocationText() { | ||||
|       return (assetDetail.exifInfo!.city != null && assetDetail.exifInfo!.state != null) | ||||
|       return (assetDetail.exifInfo!.city != null && | ||||
|               assetDetail.exifInfo!.state != null) | ||||
|           ? Text( | ||||
|               "${assetDetail.exifInfo!.city}, ${assetDetail.exifInfo!.state}", | ||||
|               style: TextStyle(fontSize: 12, color: Colors.grey[200], fontWeight: FontWeight.bold), | ||||
|               style: TextStyle( | ||||
|                   fontSize: 12, | ||||
|                   color: Colors.grey[200], | ||||
|                   fontWeight: FontWeight.bold), | ||||
|             ) | ||||
|           : Container(); | ||||
|     } | ||||
| @@ -131,7 +141,8 @@ class ExifBottomSheet extends ConsumerWidget { | ||||
|                         padding: const EdgeInsets.only(bottom: 8.0), | ||||
|                         child: Text( | ||||
|                           "DETAILS", | ||||
|                           style: TextStyle(fontSize: 11, color: Colors.grey[400]), | ||||
|                           style: | ||||
|                               TextStyle(fontSize: 11, color: Colors.grey[400]), | ||||
|                         ), | ||||
|                       ), | ||||
|                       ListTile( | ||||
| @@ -158,7 +169,8 @@ class ExifBottomSheet extends ConsumerWidget { | ||||
|                               leading: const Icon(Icons.camera), | ||||
|                               title: Text( | ||||
|                                 "${assetDetail.exifInfo?.make} ${assetDetail.exifInfo?.model}", | ||||
|                                 style: const TextStyle(fontWeight: FontWeight.bold), | ||||
|                                 style: const TextStyle( | ||||
|                                     fontWeight: FontWeight.bold), | ||||
|                               ), | ||||
|                               subtitle: Text( | ||||
|                                   "ƒ/${assetDetail.exifInfo?.fNumber}   1/${(1 / assetDetail.exifInfo!.exposureTime!).toStringAsFixed(0)}   ${assetDetail.exifInfo?.focalLength}mm   ISO${assetDetail.exifInfo?.iso} "), | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/src/widgets/framework.dart'; | ||||
| import 'package:photo_view/photo_view.dart'; | ||||
|  | ||||
| enum _RemoteImageStatus { empty, thumbnail, full } | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -5,7 +7,10 @@ import 'package:immich_mobile/shared/models/immich_asset.model.dart'; | ||||
|  | ||||
| class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget { | ||||
|   const TopControlAppBar( | ||||
|       {Key? key, required this.asset, required this.onMoreInfoPressed, required this.onDownloadPressed}) | ||||
|       {Key? key, | ||||
|       required this.asset, | ||||
|       required this.onMoreInfoPressed, | ||||
|       required this.onDownloadPressed}) | ||||
|       : super(key: key); | ||||
|  | ||||
|   final ImmichAsset asset; | ||||
| @@ -42,9 +47,11 @@ class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget { | ||||
|           iconSize: iconSize, | ||||
|           splashRadius: iconSize, | ||||
|           onPressed: () { | ||||
|             print("favorite"); | ||||
|             log("favorite"); | ||||
|           }, | ||||
|           icon: asset.isFavorite ? const Icon(Icons.favorite_rounded) : const Icon(Icons.favorite_border_rounded), | ||||
|           icon: asset.isFavorite | ||||
|               ? const Icon(Icons.favorite_rounded) | ||||
|               : const Icon(Icons.favorite_border_rounded), | ||||
|         ), | ||||
|         IconButton( | ||||
|             iconSize: iconSize, | ||||
|   | ||||
| @@ -1,16 +1,15 @@ | ||||
| import 'package:cancellation_token_http/http.dart'; | ||||
| import 'package:dio/dio.dart'; | ||||
| import 'package:flutter/foundation.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/backup/models/available_album.model.dart'; | ||||
| import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart'; | ||||
| import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | ||||
| import 'package:immich_mobile/shared/services/server_info.service.dart'; | ||||
| import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; | ||||
| import 'package:immich_mobile/shared/models/server_info.model.dart'; | ||||
| import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart'; | ||||
| import 'package:immich_mobile/modules/backup/services/backup.service.dart'; | ||||
| import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | ||||
| import 'package:immich_mobile/shared/models/server_info.model.dart'; | ||||
| import 'package:immich_mobile/shared/services/server_info.service.dart'; | ||||
| import 'package:photo_manager/photo_manager.dart'; | ||||
|  | ||||
| class BackupNotifier extends StateNotifier<BackUpState> { | ||||
| @@ -55,7 +54,8 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|       removeExcludedAlbumForBackup(album); | ||||
|     } | ||||
|  | ||||
|     state = state.copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, album}); | ||||
|     state = state | ||||
|         .copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, album}); | ||||
|     _updateBackupAssetCount(); | ||||
|   } | ||||
|  | ||||
| @@ -63,7 +63,8 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|     if (state.selectedBackupAlbums.contains(album)) { | ||||
|       removeAlbumForBackup(album); | ||||
|     } | ||||
|     state = state.copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, album}); | ||||
|     state = state | ||||
|         .copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, album}); | ||||
|     _updateBackupAssetCount(); | ||||
|   } | ||||
|  | ||||
| @@ -94,16 +95,19 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|   Future<void> getBackupAlbumsInfo() async { | ||||
|     // Get all albums on the device | ||||
|     List<AvailableAlbum> availableAlbums = []; | ||||
|     List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(hasAll: true, type: RequestType.common); | ||||
|     List<AssetPathEntity> albums = await PhotoManager.getAssetPathList( | ||||
|         hasAll: true, type: RequestType.common); | ||||
|  | ||||
|     for (AssetPathEntity album in albums) { | ||||
|       AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album); | ||||
|  | ||||
|       var assetList = await album.getAssetListRange(start: 0, end: album.assetCount); | ||||
|       var assetList = | ||||
|           await album.getAssetListRange(start: 0, end: album.assetCount); | ||||
|  | ||||
|       if (assetList.isNotEmpty) { | ||||
|         var thumbnailAsset = assetList.first; | ||||
|         var thumbnailData = await thumbnailAsset.thumbnailDataWithSize(const ThumbnailSize(512, 512)); | ||||
|         var thumbnailData = await thumbnailAsset | ||||
|             .thumbnailDataWithSize(const ThumbnailSize(512, 512)); | ||||
|         availableAlbum = availableAlbum.copyWith(thumbnailData: thumbnailData); | ||||
|       } | ||||
|  | ||||
| @@ -114,7 +118,8 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|  | ||||
|     // Put persistent storage info into local state of the app | ||||
|     // Get local storage on selected backup album | ||||
|     Box<HiveBackupAlbums> backupAlbumInfoBox = Hive.box<HiveBackupAlbums>(hiveBackupInfoBox); | ||||
|     Box<HiveBackupAlbums> backupAlbumInfoBox = | ||||
|         Hive.box<HiveBackupAlbums>(hiveBackupInfoBox); | ||||
|     HiveBackupAlbums? backupAlbumInfo = backupAlbumInfoBox.get( | ||||
|       backupInfoKey, | ||||
|       defaultValue: HiveBackupAlbums( | ||||
| @@ -133,7 +138,8 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|       debugPrint("First time backup setup recent album as default"); | ||||
|  | ||||
|       // Get album that contains all assets | ||||
|       var list = await PhotoManager.getAssetPathList(hasAll: true, onlyAll: true, type: RequestType.common); | ||||
|       var list = await PhotoManager.getAssetPathList( | ||||
|           hasAll: true, onlyAll: true, type: RequestType.common); | ||||
|       AssetPathEntity albumHasAllAssets = list.first; | ||||
|  | ||||
|       backupAlbumInfoBox.put( | ||||
| @@ -151,12 +157,14 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|     try { | ||||
|       for (var selectedAlbumId in backupAlbumInfo!.selectedAlbumIds) { | ||||
|         var albumAsset = await AssetPathEntity.fromId(selectedAlbumId); | ||||
|         state = state.copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, albumAsset}); | ||||
|         state = state.copyWith( | ||||
|             selectedBackupAlbums: {...state.selectedBackupAlbums, albumAsset}); | ||||
|       } | ||||
|  | ||||
|       for (var excludedAlbumId in backupAlbumInfo.excludedAlbumsIds) { | ||||
|         var albumAsset = await AssetPathEntity.fromId(excludedAlbumId); | ||||
|         state = state.copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, albumAsset}); | ||||
|         state = state.copyWith( | ||||
|             excludedBackupAlbums: {...state.excludedBackupAlbums, albumAsset}); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       debugPrint("[ERROR] Failed to generate album from id $e"); | ||||
| @@ -173,21 +181,27 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|     Set<AssetEntity> assetsFromExcludedAlbums = {}; | ||||
|  | ||||
|     for (var album in state.selectedBackupAlbums) { | ||||
|       var assets = await album.getAssetListRange(start: 0, end: album.assetCount); | ||||
|       var assets = | ||||
|           await album.getAssetListRange(start: 0, end: album.assetCount); | ||||
|       assetsFromSelectedAlbums.addAll(assets); | ||||
|     } | ||||
|  | ||||
|     for (var album in state.excludedBackupAlbums) { | ||||
|       var assets = await album.getAssetListRange(start: 0, end: album.assetCount); | ||||
|       var assets = | ||||
|           await album.getAssetListRange(start: 0, end: album.assetCount); | ||||
|       assetsFromExcludedAlbums.addAll(assets); | ||||
|     } | ||||
|  | ||||
|     Set<AssetEntity> allUniqueAssets = assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums); | ||||
|     List<String> allAssetOnDatabase = await _backupService.getDeviceBackupAsset(); | ||||
|     Set<AssetEntity> allUniqueAssets = | ||||
|         assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums); | ||||
|     List<String> allAssetOnDatabase = | ||||
|         await _backupService.getDeviceBackupAsset(); | ||||
|  | ||||
|     // Find asset that were backup from selected albums | ||||
|     Set<String> selectedAlbumsBackupAssets = Set.from(allUniqueAssets.map((e) => e.id)); | ||||
|     selectedAlbumsBackupAssets.removeWhere((assetId) => !allAssetOnDatabase.contains(assetId)); | ||||
|     Set<String> selectedAlbumsBackupAssets = | ||||
|         Set.from(allUniqueAssets.map((e) => e.id)); | ||||
|     selectedAlbumsBackupAssets | ||||
|         .removeWhere((assetId) => !allAssetOnDatabase.contains(assetId)); | ||||
|  | ||||
|     if (allUniqueAssets.isEmpty) { | ||||
|       debugPrint("No Asset On Device"); | ||||
| @@ -226,7 +240,8 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|   /// Hive database | ||||
|   /// | ||||
|   void _updatePersistentAlbumsSelection() { | ||||
|     Box<HiveBackupAlbums> backupAlbumInfoBox = Hive.box<HiveBackupAlbums>(hiveBackupInfoBox); | ||||
|     Box<HiveBackupAlbums> backupAlbumInfoBox = | ||||
|         Hive.box<HiveBackupAlbums>(hiveBackupInfoBox); | ||||
|     backupAlbumInfoBox.put( | ||||
|       backupInfoKey, | ||||
|       HiveBackupAlbums( | ||||
| @@ -268,7 +283,8 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|  | ||||
|       // Perform Backup | ||||
|       state = state.copyWith(cancelToken: CancellationToken()); | ||||
|       _backupService.backupAsset(assetsWillBeBackup, state.cancelToken, _onAssetUploaded, _onUploadProgress); | ||||
|       _backupService.backupAsset(assetsWillBeBackup, state.cancelToken, | ||||
|           _onAssetUploaded, _onUploadProgress); | ||||
|     } else { | ||||
|       PhotoManager.openSetting(); | ||||
|     } | ||||
| @@ -276,23 +292,32 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|  | ||||
|   void cancelBackup() { | ||||
|     state.cancelToken.cancel(); | ||||
|     state = state.copyWith(backupProgress: BackUpProgressEnum.idle, progressInPercentage: 0.0); | ||||
|     state = state.copyWith( | ||||
|         backupProgress: BackUpProgressEnum.idle, progressInPercentage: 0.0); | ||||
|   } | ||||
|  | ||||
|   void _onAssetUploaded(String deviceAssetId, String deviceId) { | ||||
|     state = state.copyWith( | ||||
|         selectedAlbumsBackupAssetsIds: {...state.selectedAlbumsBackupAssetsIds, deviceAssetId}, | ||||
|         allAssetOnDatabase: [...state.allAssetOnDatabase, deviceAssetId]); | ||||
|     state = state.copyWith(selectedAlbumsBackupAssetsIds: { | ||||
|       ...state.selectedAlbumsBackupAssetsIds, | ||||
|       deviceAssetId | ||||
|     }, allAssetOnDatabase: [ | ||||
|       ...state.allAssetOnDatabase, | ||||
|       deviceAssetId | ||||
|     ]); | ||||
|  | ||||
|     if (state.allUniqueAssets.length - state.selectedAlbumsBackupAssetsIds.length == 0) { | ||||
|       state = state.copyWith(backupProgress: BackUpProgressEnum.done, progressInPercentage: 0.0); | ||||
|     if (state.allUniqueAssets.length - | ||||
|             state.selectedAlbumsBackupAssetsIds.length == | ||||
|         0) { | ||||
|       state = state.copyWith( | ||||
|           backupProgress: BackUpProgressEnum.done, progressInPercentage: 0.0); | ||||
|     } | ||||
|  | ||||
|     _updateServerInfo(); | ||||
|   } | ||||
|  | ||||
|   void _onUploadProgress(int sent, int total) { | ||||
|     state = state.copyWith(progressInPercentage: (sent.toDouble() / total.toDouble() * 100)); | ||||
|     state = state.copyWith( | ||||
|         progressInPercentage: (sent.toDouble() / total.toDouble() * 100)); | ||||
|   } | ||||
|  | ||||
|   void _updateServerInfo() async { | ||||
| @@ -326,7 +351,8 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|       } | ||||
|  | ||||
|       // Check if this device is enable backup by the user | ||||
|       if ((authState.deviceInfo.deviceId == authState.deviceId) && authState.deviceInfo.isAutoBackup) { | ||||
|       if ((authState.deviceInfo.deviceId == authState.deviceId) && | ||||
|           authState.deviceInfo.isAutoBackup) { | ||||
|         // check if backup is alreayd in process - then return | ||||
|         if (state.backupProgress == BackUpProgressEnum.inProgress) { | ||||
|           debugPrint("[resumeBackup] Backup is already in progress - abort"); | ||||
| @@ -343,6 +369,7 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|   } | ||||
| } | ||||
|  | ||||
| final backupProvider = StateNotifierProvider<BackupNotifier, BackUpState>((ref) { | ||||
| final backupProvider = | ||||
|     StateNotifierProvider<BackupNotifier, BackUpState>((ref) { | ||||
|   return BackupNotifier(ref: ref); | ||||
| }); | ||||
|   | ||||
| @@ -17,16 +17,21 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     BackUpState backupState = ref.watch(backupProvider); | ||||
|     AuthenticationState _authenticationState = ref.watch(authenticationProvider); | ||||
|     bool shouldBackup = | ||||
|         backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length == 0 ? false : true; | ||||
|     AuthenticationState authenticationState = ref.watch(authenticationProvider); | ||||
|     bool shouldBackup = backupState.allUniqueAssets.length - | ||||
|                 backupState.selectedAlbumsBackupAssetsIds.length == | ||||
|             0 | ||||
|         ? false | ||||
|         : true; | ||||
|  | ||||
|     useEffect(() { | ||||
|       if (backupState.backupProgress != BackUpProgressEnum.inProgress) { | ||||
|         ref.read(backupProvider.notifier).getBackupInfo(); | ||||
|       } | ||||
|  | ||||
|       ref.watch(websocketProvider.notifier).stopListenToEvent('on_upload_success'); | ||||
|       ref | ||||
|           .watch(websocketProvider.notifier) | ||||
|           .stopListenToEvent('on_upload_success'); | ||||
|       return null; | ||||
|     }, []); | ||||
|  | ||||
| @@ -48,7 +53,8 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|               Padding( | ||||
|                 padding: const EdgeInsets.only(top: 8.0), | ||||
|                 child: LinearPercentIndicator( | ||||
|                   padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0), | ||||
|                   padding: | ||||
|                       const EdgeInsets.symmetric(horizontal: 0, vertical: 0), | ||||
|                   barRadius: const Radius.circular(2), | ||||
|                   lineHeight: 6.0, | ||||
|                   percent: backupState.serverInfo.diskUsagePercentage / 100.0, | ||||
| @@ -58,7 +64,8 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|               ), | ||||
|               Padding( | ||||
|                 padding: const EdgeInsets.only(top: 12.0), | ||||
|                 child: Text('${backupState.serverInfo.diskUse} of ${backupState.serverInfo.diskSize} used'), | ||||
|                 child: Text( | ||||
|                     '${backupState.serverInfo.diskUse} of ${backupState.serverInfo.diskSize} used'), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
| @@ -67,9 +74,11 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     ListTile _buildBackupController() { | ||||
|       var backUpOption = _authenticationState.deviceInfo.isAutoBackup ? "on" : "off"; | ||||
|       var isAutoBackup = _authenticationState.deviceInfo.isAutoBackup; | ||||
|       var backupBtnText = _authenticationState.deviceInfo.isAutoBackup ? "off" : "on"; | ||||
|       var backUpOption = | ||||
|           authenticationState.deviceInfo.isAutoBackup ? "on" : "off"; | ||||
|       var isAutoBackup = authenticationState.deviceInfo.isAutoBackup; | ||||
|       var backupBtnText = | ||||
|           authenticationState.deviceInfo.isAutoBackup ? "off" : "on"; | ||||
|       return ListTile( | ||||
|         isThreeLine: true, | ||||
|         leading: isAutoBackup | ||||
| @@ -104,10 +113,15 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|                   ), | ||||
|                   onPressed: () { | ||||
|                     isAutoBackup | ||||
|                         ? ref.watch(authenticationProvider.notifier).setAutoBackup(false) | ||||
|                         : ref.watch(authenticationProvider.notifier).setAutoBackup(true); | ||||
|                         ? ref | ||||
|                             .watch(authenticationProvider.notifier) | ||||
|                             .setAutoBackup(false) | ||||
|                         : ref | ||||
|                             .watch(authenticationProvider.notifier) | ||||
|                             .setAutoBackup(true); | ||||
|                   }, | ||||
|                   child: Text("Turn $backupBtnText Backup", style: const TextStyle(fontWeight: FontWeight.bold)), | ||||
|                   child: Text("Turn $backupBtnText Backup", | ||||
|                       style: const TextStyle(fontWeight: FontWeight.bold)), | ||||
|                 ), | ||||
|               ) | ||||
|             ], | ||||
| @@ -133,7 +147,10 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|           padding: const EdgeInsets.only(top: 8.0), | ||||
|           child: Text( | ||||
|             text.trim().substring(0, text.length - 2), | ||||
|             style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12, fontWeight: FontWeight.bold), | ||||
|             style: TextStyle( | ||||
|                 color: Theme.of(context).primaryColor, | ||||
|                 fontSize: 12, | ||||
|                 fontWeight: FontWeight.bold), | ||||
|           ), | ||||
|         ); | ||||
|       } else { | ||||
| @@ -141,7 +158,10 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|           padding: const EdgeInsets.only(top: 8.0), | ||||
|           child: Text( | ||||
|             "None selected", | ||||
|             style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12, fontWeight: FontWeight.bold), | ||||
|             style: TextStyle( | ||||
|                 color: Theme.of(context).primaryColor, | ||||
|                 fontSize: 12, | ||||
|                 fontWeight: FontWeight.bold), | ||||
|           ), | ||||
|         ); | ||||
|       } | ||||
| @@ -160,7 +180,10 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|           padding: const EdgeInsets.only(top: 8.0), | ||||
|           child: Text( | ||||
|             text.trim().substring(0, text.length - 2), | ||||
|             style: TextStyle(color: Colors.red[300], fontSize: 12, fontWeight: FontWeight.bold), | ||||
|             style: TextStyle( | ||||
|                 color: Colors.red[300], | ||||
|                 fontSize: 12, | ||||
|                 fontWeight: FontWeight.bold), | ||||
|           ), | ||||
|         ); | ||||
|       } else { | ||||
| @@ -181,7 +204,8 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|         borderOnForeground: false, | ||||
|         child: ListTile( | ||||
|           minVerticalPadding: 15, | ||||
|           title: const Text("Backup Albums", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), | ||||
|           title: const Text("Backup Albums", | ||||
|               style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)), | ||||
|           subtitle: Padding( | ||||
|             padding: const EdgeInsets.only(top: 8.0), | ||||
|             child: Column( | ||||
| @@ -258,13 +282,16 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|             ), | ||||
|             BackupInfoCard( | ||||
|               title: "Backup", | ||||
|               subtitle: "Photos and videos from selected albums that are backup", | ||||
|               subtitle: | ||||
|                   "Photos and videos from selected albums that are backup", | ||||
|               info: "${backupState.selectedAlbumsBackupAssetsIds.length}", | ||||
|             ), | ||||
|             BackupInfoCard( | ||||
|               title: "Remainder", | ||||
|               subtitle: "Photos and videos that has not been backing up from selected albums", | ||||
|               info: "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}", | ||||
|               subtitle: | ||||
|                   "Photos and videos that has not been backing up from selected albums", | ||||
|               info: | ||||
|                   "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}", | ||||
|             ), | ||||
|             const Divider(), | ||||
|             _buildBackupController(), | ||||
| @@ -289,29 +316,32 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|             Padding( | ||||
|               padding: const EdgeInsets.all(8.0), | ||||
|               child: Container( | ||||
|                 child: backupState.backupProgress == BackUpProgressEnum.inProgress | ||||
|                     ? ElevatedButton( | ||||
|                         style: ElevatedButton.styleFrom( | ||||
|                           primary: Colors.red[300], | ||||
|                           onPrimary: Colors.grey[50], | ||||
|                         ), | ||||
|                         onPressed: () { | ||||
|                           ref.read(backupProvider.notifier).cancelBackup(); | ||||
|                         }, | ||||
|                         child: const Text("Cancel"), | ||||
|                       ) | ||||
|                     : ElevatedButton( | ||||
|                         style: ElevatedButton.styleFrom( | ||||
|                           primary: Theme.of(context).primaryColor, | ||||
|                           onPrimary: Colors.grey[50], | ||||
|                         ), | ||||
|                         onPressed: shouldBackup | ||||
|                             ? () { | ||||
|                                 ref.read(backupProvider.notifier).startBackupProcess(); | ||||
|                               } | ||||
|                             : null, | ||||
|                         child: const Text("Start Backup"), | ||||
|                       ), | ||||
|                 child: | ||||
|                     backupState.backupProgress == BackUpProgressEnum.inProgress | ||||
|                         ? ElevatedButton( | ||||
|                             style: ElevatedButton.styleFrom( | ||||
|                               primary: Colors.red[300], | ||||
|                               onPrimary: Colors.grey[50], | ||||
|                             ), | ||||
|                             onPressed: () { | ||||
|                               ref.read(backupProvider.notifier).cancelBackup(); | ||||
|                             }, | ||||
|                             child: const Text("Cancel"), | ||||
|                           ) | ||||
|                         : ElevatedButton( | ||||
|                             style: ElevatedButton.styleFrom( | ||||
|                               primary: Theme.of(context).primaryColor, | ||||
|                               onPrimary: Colors.grey[50], | ||||
|                             ), | ||||
|                             onPressed: shouldBackup | ||||
|                                 ? () { | ||||
|                                     ref | ||||
|                                         .read(backupProvider.notifier) | ||||
|                                         .startBackupProcess(); | ||||
|                                   } | ||||
|                                 : null, | ||||
|                             child: const Text("Start Backup"), | ||||
|                           ), | ||||
|               ), | ||||
|             ) | ||||
|           ], | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart'; | ||||
|  | ||||
| class DisableMultiSelectButton extends ConsumerWidget { | ||||
|   const DisableMultiSelectButton({ | ||||
| @@ -36,7 +35,8 @@ class DisableMultiSelectButton extends ConsumerWidget { | ||||
|                   icon: const Icon(Icons.close_rounded), | ||||
|                   label: Text( | ||||
|                     selectedItemCount.toString(), | ||||
|                     style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18), | ||||
|                     style: const TextStyle( | ||||
|                         fontWeight: FontWeight.w600, fontSize: 18), | ||||
|                   )), | ||||
|             ), | ||||
|           ), | ||||
|   | ||||
| @@ -81,7 +81,8 @@ class DraggableScrollbar extends StatefulWidget { | ||||
|     this.labelTextBuilder, | ||||
|     this.labelConstraints, | ||||
|   })  : assert(child.scrollDirection == Axis.vertical), | ||||
|         scrollThumbBuilder = _thumbRRectBuilder(scrollThumbKey, alwaysVisibleScrollThumb), | ||||
|         scrollThumbBuilder = | ||||
|             _thumbRRectBuilder(scrollThumbKey, alwaysVisibleScrollThumb), | ||||
|         super(key: key); | ||||
|  | ||||
|   DraggableScrollbar.arrows({ | ||||
| @@ -98,7 +99,8 @@ class DraggableScrollbar extends StatefulWidget { | ||||
|     this.labelTextBuilder, | ||||
|     this.labelConstraints, | ||||
|   })  : assert(child.scrollDirection == Axis.vertical), | ||||
|         scrollThumbBuilder = _thumbArrowBuilder(scrollThumbKey, alwaysVisibleScrollThumb), | ||||
|         scrollThumbBuilder = | ||||
|             _thumbArrowBuilder(scrollThumbKey, alwaysVisibleScrollThumb), | ||||
|         super(key: key); | ||||
|  | ||||
|   DraggableScrollbar.semicircle({ | ||||
| @@ -115,11 +117,12 @@ class DraggableScrollbar extends StatefulWidget { | ||||
|     this.labelTextBuilder, | ||||
|     this.labelConstraints, | ||||
|   })  : assert(child.scrollDirection == Axis.vertical), | ||||
|         scrollThumbBuilder = _thumbSemicircleBuilder(heightScrollThumb * 0.6, scrollThumbKey, alwaysVisibleScrollThumb), | ||||
|         scrollThumbBuilder = _thumbSemicircleBuilder( | ||||
|             heightScrollThumb * 0.6, scrollThumbKey, alwaysVisibleScrollThumb), | ||||
|         super(key: key); | ||||
|  | ||||
|   @override | ||||
|   _DraggableScrollbarState createState() => _DraggableScrollbarState(); | ||||
|   DraggableScrollbarState createState() => DraggableScrollbarState(); | ||||
|  | ||||
|   static buildScrollThumbAndLabel( | ||||
|       {required Widget scrollThumb, | ||||
| @@ -137,9 +140,9 @@ class DraggableScrollbar extends StatefulWidget { | ||||
|             children: [ | ||||
|               ScrollLabel( | ||||
|                 animation: labelAnimation, | ||||
|                 child: labelText, | ||||
|                 backgroundColor: backgroundColor, | ||||
|                 constraints: labelConstraints, | ||||
|                 child: labelText, | ||||
|               ), | ||||
|               scrollThumb, | ||||
|             ], | ||||
| @@ -154,7 +157,8 @@ class DraggableScrollbar extends StatefulWidget { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   static ScrollThumbBuilder _thumbSemicircleBuilder(double width, Key? scrollThumbKey, bool alwaysVisibleScrollThumb) { | ||||
|   static ScrollThumbBuilder _thumbSemicircleBuilder( | ||||
|       double width, Key? scrollThumbKey, bool alwaysVisibleScrollThumb) { | ||||
|     return ( | ||||
|       Color backgroundColor, | ||||
|       Animation<double> thumbAnimation, | ||||
| @@ -168,9 +172,6 @@ class DraggableScrollbar extends StatefulWidget { | ||||
|         foregroundPainter: ArrowCustomPainter(Colors.white), | ||||
|         child: Material( | ||||
|           elevation: 4.0, | ||||
|           child: Container( | ||||
|             constraints: BoxConstraints.tight(Size(width, height)), | ||||
|           ), | ||||
|           color: backgroundColor, | ||||
|           borderRadius: BorderRadius.only( | ||||
|             topLeft: Radius.circular(height), | ||||
| @@ -178,6 +179,9 @@ class DraggableScrollbar extends StatefulWidget { | ||||
|             topRight: const Radius.circular(4.0), | ||||
|             bottomRight: const Radius.circular(4.0), | ||||
|           ), | ||||
|           child: Container( | ||||
|             constraints: BoxConstraints.tight(Size(width, height)), | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|  | ||||
| @@ -193,7 +197,8 @@ class DraggableScrollbar extends StatefulWidget { | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   static ScrollThumbBuilder _thumbArrowBuilder(Key? scrollThumbKey, bool alwaysVisibleScrollThumb) { | ||||
|   static ScrollThumbBuilder _thumbArrowBuilder( | ||||
|       Key? scrollThumbKey, bool alwaysVisibleScrollThumb) { | ||||
|     return ( | ||||
|       Color backgroundColor, | ||||
|       Animation<double> thumbAnimation, | ||||
| @@ -203,6 +208,7 @@ class DraggableScrollbar extends StatefulWidget { | ||||
|       BoxConstraints? labelConstraints, | ||||
|     }) { | ||||
|       final scrollThumb = ClipPath( | ||||
|         clipper: ArrowClipper(), | ||||
|         child: Container( | ||||
|           height: height, | ||||
|           width: 20.0, | ||||
| @@ -213,7 +219,6 @@ class DraggableScrollbar extends StatefulWidget { | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|         clipper: ArrowClipper(), | ||||
|       ); | ||||
|  | ||||
|       return buildScrollThumbAndLabel( | ||||
| @@ -228,7 +233,8 @@ class DraggableScrollbar extends StatefulWidget { | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   static ScrollThumbBuilder _thumbRRectBuilder(Key? scrollThumbKey, bool alwaysVisibleScrollThumb) { | ||||
|   static ScrollThumbBuilder _thumbRRectBuilder( | ||||
|       Key? scrollThumbKey, bool alwaysVisibleScrollThumb) { | ||||
|     return ( | ||||
|       Color backgroundColor, | ||||
|       Animation<double> thumbAnimation, | ||||
| @@ -239,13 +245,13 @@ class DraggableScrollbar extends StatefulWidget { | ||||
|     }) { | ||||
|       final scrollThumb = Material( | ||||
|         elevation: 4.0, | ||||
|         color: backgroundColor, | ||||
|         borderRadius: const BorderRadius.all(Radius.circular(7.0)), | ||||
|         child: Container( | ||||
|           constraints: BoxConstraints.tight( | ||||
|             Size(16.0, height), | ||||
|           ), | ||||
|         ), | ||||
|         color: backgroundColor, | ||||
|         borderRadius: const BorderRadius.all(Radius.circular(7.0)), | ||||
|       ); | ||||
|  | ||||
|       return buildScrollThumbAndLabel( | ||||
| @@ -267,7 +273,8 @@ class ScrollLabel extends StatelessWidget { | ||||
|   final Text child; | ||||
|  | ||||
|   final BoxConstraints? constraints; | ||||
|   static const BoxConstraints _defaultConstraints = BoxConstraints.tightFor(width: 72.0, height: 28.0); | ||||
|   static const BoxConstraints _defaultConstraints = | ||||
|       BoxConstraints.tightFor(width: 72.0, height: 28.0); | ||||
|  | ||||
|   const ScrollLabel({ | ||||
|     Key? key, | ||||
| @@ -298,7 +305,8 @@ class ScrollLabel extends StatelessWidget { | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProviderStateMixin { | ||||
| class DraggableScrollbarState extends State<DraggableScrollbar> | ||||
|     with TickerProviderStateMixin { | ||||
|   late double _barOffset; | ||||
|   late double _viewOffset; | ||||
|   late bool _isDragInProcess; | ||||
| @@ -345,7 +353,8 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv | ||||
|     super.dispose(); | ||||
|   } | ||||
|  | ||||
|   double get barMaxScrollExtent => context.size!.height - widget.heightScrollThumb; | ||||
|   double get barMaxScrollExtent => | ||||
|       context.size!.height - widget.heightScrollThumb; | ||||
|  | ||||
|   double get barMinScrollExtent => 0; | ||||
|  | ||||
| @@ -362,7 +371,8 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { | ||||
|     return LayoutBuilder( | ||||
|         builder: (BuildContext context, BoxConstraints constraints) { | ||||
|       //print("LayoutBuilder constraints=$constraints"); | ||||
|  | ||||
|       return NotificationListener<ScrollNotification>( | ||||
| @@ -432,7 +442,8 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (notification is ScrollUpdateNotification || notification is OverscrollNotification) { | ||||
|       if (notification is ScrollUpdateNotification || | ||||
|           notification is OverscrollNotification) { | ||||
|         if (_thumbAnimationController.status != AnimationStatus.forward) { | ||||
|           _thumbAnimationController.forward(); | ||||
|         } | ||||
| @@ -486,7 +497,8 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv | ||||
|           _barOffset = barMaxScrollExtent; | ||||
|         } | ||||
|  | ||||
|         double viewDelta = getScrollViewDelta(details.delta.dy, barMaxScrollExtent, viewMaxScrollExtent); | ||||
|         double viewDelta = getScrollViewDelta( | ||||
|             details.delta.dy, barMaxScrollExtent, viewMaxScrollExtent); | ||||
|  | ||||
|         _viewOffset = widget.controller.position.pixels + viewDelta; | ||||
|         if (_viewOffset < widget.controller.position.minScrollExtent) { | ||||
| @@ -566,7 +578,8 @@ class ArrowClipper extends CustomClipper<Path> { | ||||
|     path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2); | ||||
|     path.lineTo(startPointX + arrowWidth, startPointY); | ||||
|     path.lineTo(startPointX + arrowWidth, startPointY + 1.0); | ||||
|     path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2 + 1.0); | ||||
|     path.lineTo( | ||||
|         startPointX + arrowWidth / 2, startPointY - arrowWidth / 2 + 1.0); | ||||
|     path.lineTo(startPointX, startPointY + 1.0); | ||||
|     path.close(); | ||||
|  | ||||
| @@ -575,7 +588,8 @@ class ArrowClipper extends CustomClipper<Path> { | ||||
|     path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2); | ||||
|     path.lineTo(startPointX, startPointY); | ||||
|     path.lineTo(startPointX, startPointY - 1.0); | ||||
|     path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2 - 1.0); | ||||
|     path.lineTo( | ||||
|         startPointX + arrowWidth / 2, startPointY + arrowWidth / 2 - 1.0); | ||||
|     path.lineTo(startPointX + arrowWidth, startPointY - 1.0); | ||||
|     path.close(); | ||||
|  | ||||
| @@ -600,7 +614,8 @@ class SlideFadeTransition extends StatelessWidget { | ||||
|   Widget build(BuildContext context) { | ||||
|     return AnimatedBuilder( | ||||
|       animation: animation, | ||||
|       builder: (context, child) => animation.value == 0.0 ? Container() : child!, | ||||
|       builder: (context, child) => | ||||
|           animation.value == 0.0 ? Container() : child!, | ||||
|       child: SlideTransition( | ||||
|         position: Tween( | ||||
|           begin: const Offset(0.3, 0.0), | ||||
|   | ||||
| @@ -20,16 +20,18 @@ class ImmichSliverAppBar extends ConsumerWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final BackUpState _backupState = ref.watch(backupProvider); | ||||
|     bool _isEnableAutoBackup = ref.watch(authenticationProvider).deviceInfo.isAutoBackup; | ||||
|     final ServerInfoState _serverInfoState = ref.watch(serverInfoProvider); | ||||
|     final BackUpState backupState = ref.watch(backupProvider); | ||||
|     bool isEnableAutoBackup = | ||||
|         ref.watch(authenticationProvider).deviceInfo.isAutoBackup; | ||||
|     final ServerInfoState serverInfoState = ref.watch(serverInfoProvider); | ||||
|  | ||||
|     return SliverAppBar( | ||||
|       centerTitle: true, | ||||
|       floating: true, | ||||
|       pinned: false, | ||||
|       snap: false, | ||||
|       shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5))), | ||||
|       shape: const RoundedRectangleBorder( | ||||
|           borderRadius: BorderRadius.all(Radius.circular(5))), | ||||
|       leading: Builder( | ||||
|         builder: (BuildContext context) { | ||||
|           return Stack( | ||||
| @@ -47,7 +49,7 @@ class ImmichSliverAppBar extends ConsumerWidget { | ||||
|                   }, | ||||
|                 ), | ||||
|               ), | ||||
|               _serverInfoState.isVersionMismatch | ||||
|               serverInfoState.isVersionMismatch | ||||
|                   ? Positioned( | ||||
|                       bottom: 12, | ||||
|                       right: 12, | ||||
| @@ -88,7 +90,7 @@ class ImmichSliverAppBar extends ConsumerWidget { | ||||
|         Stack( | ||||
|           alignment: AlignmentDirectional.center, | ||||
|           children: [ | ||||
|             _backupState.backupProgress == BackUpProgressEnum.inProgress | ||||
|             backupState.backupProgress == BackUpProgressEnum.inProgress | ||||
|                 ? Positioned( | ||||
|                     top: 10, | ||||
|                     right: 12, | ||||
| @@ -97,7 +99,8 @@ class ImmichSliverAppBar extends ConsumerWidget { | ||||
|                       width: 8, | ||||
|                       child: CircularProgressIndicator( | ||||
|                         strokeWidth: 1, | ||||
|                         valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor), | ||||
|                         valueColor: AlwaysStoppedAnimation<Color>( | ||||
|                             Theme.of(context).primaryColor), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ) | ||||
| @@ -105,7 +108,7 @@ class ImmichSliverAppBar extends ConsumerWidget { | ||||
|             IconButton( | ||||
|               splashRadius: 25, | ||||
|               iconSize: 30, | ||||
|               icon: _isEnableAutoBackup | ||||
|               icon: isEnableAutoBackup | ||||
|                   ? const Icon(Icons.backup_rounded) | ||||
|                   : Badge( | ||||
|                       padding: const EdgeInsets.all(4), | ||||
| @@ -118,20 +121,23 @@ class ImmichSliverAppBar extends ConsumerWidget { | ||||
|                       ), | ||||
|                       child: const Icon(Icons.backup_rounded)), | ||||
|               onPressed: () async { | ||||
|                 var onPop = await AutoRouter.of(context).push(const BackupControllerRoute()); | ||||
|                 var onPop = await AutoRouter.of(context) | ||||
|                     .push(const BackupControllerRoute()); | ||||
|  | ||||
|                 if (onPop != null && onPop == true) { | ||||
|                   onPopBack!(); | ||||
|                 } | ||||
|               }, | ||||
|             ), | ||||
|             _backupState.backupProgress == BackUpProgressEnum.inProgress | ||||
|             backupState.backupProgress == BackUpProgressEnum.inProgress | ||||
|                 ? Positioned( | ||||
|                     bottom: 5, | ||||
|                     child: Text( | ||||
|                       (_backupState.allUniqueAssets.length - _backupState.selectedAlbumsBackupAssetsIds.length) | ||||
|                       (backupState.allUniqueAssets.length - | ||||
|                               backupState.selectedAlbumsBackupAssetsIds.length) | ||||
|                           .toString(), | ||||
|                       style: const TextStyle(fontSize: 9, fontWeight: FontWeight.bold), | ||||
|                       style: const TextStyle( | ||||
|                           fontSize: 9, fontWeight: FontWeight.bold), | ||||
|                     ), | ||||
|                   ) | ||||
|                 : Container() | ||||
|   | ||||
| @@ -24,9 +24,10 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     String endpoint = Hive.box(userInfoBox).get(serverEndpointKey); | ||||
|     AuthenticationState _authState = ref.watch(authenticationProvider); | ||||
|     ServerInfoState _serverInfoState = ref.watch(serverInfoProvider); | ||||
|     final uploadProfileImageStatus = ref.watch(uploadProfileImageProvider).status; | ||||
|     AuthenticationState authState = ref.watch(authenticationProvider); | ||||
|     ServerInfoState serverInfoState = ref.watch(serverInfoProvider); | ||||
|     final uploadProfileImageStatus = | ||||
|         ref.watch(uploadProfileImageProvider).status; | ||||
|     final appInfo = useState({}); | ||||
|     var dummmy = Random().nextInt(1024); | ||||
|  | ||||
| @@ -40,7 +41,7 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     _buildUserProfileImage() { | ||||
|       if (_authState.profileImagePath.isEmpty) { | ||||
|       if (authState.profileImagePath.isEmpty) { | ||||
|         return const CircleAvatar( | ||||
|           radius: 35, | ||||
|           backgroundImage: AssetImage('assets/immich-logo-no-outline.png'), | ||||
| @@ -49,10 +50,11 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|       } | ||||
|  | ||||
|       if (uploadProfileImageStatus == UploadProfileStatus.idle) { | ||||
|         if (_authState.profileImagePath.isNotEmpty) { | ||||
|         if (authState.profileImagePath.isNotEmpty) { | ||||
|           return CircleAvatar( | ||||
|             radius: 35, | ||||
|             backgroundImage: NetworkImage('$endpoint/user/profile-image/${_authState.userId}?d=${dummmy++}'), | ||||
|             backgroundImage: NetworkImage( | ||||
|                 '$endpoint/user/profile-image/${authState.userId}?d=${dummmy++}'), | ||||
|             backgroundColor: Colors.transparent, | ||||
|           ); | ||||
|         } else { | ||||
| @@ -67,7 +69,8 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|       if (uploadProfileImageStatus == UploadProfileStatus.success) { | ||||
|         return CircleAvatar( | ||||
|           radius: 35, | ||||
|           backgroundImage: NetworkImage('$endpoint/user/profile-image/${_authState.userId}?d=${dummmy++}'), | ||||
|           backgroundImage: NetworkImage( | ||||
|               '$endpoint/user/profile-image/${authState.userId}?d=${dummmy++}'), | ||||
|           backgroundColor: Colors.transparent, | ||||
|         ); | ||||
|       } | ||||
| @@ -88,15 +91,16 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     _pickUserProfileImage() async { | ||||
|       final XFile? image = await ImagePicker().pickImage(source: ImageSource.gallery, maxHeight: 1024, maxWidth: 1024); | ||||
|       final XFile? image = await ImagePicker().pickImage( | ||||
|           source: ImageSource.gallery, maxHeight: 1024, maxWidth: 1024); | ||||
|  | ||||
|       if (image != null) { | ||||
|         var success = await ref.watch(uploadProfileImageProvider.notifier).upload(image); | ||||
|         var success = | ||||
|             await ref.watch(uploadProfileImageProvider.notifier).upload(image); | ||||
|  | ||||
|         if (success) { | ||||
|           ref | ||||
|               .watch(authenticationProvider.notifier) | ||||
|               .updateUserProfileImagePath(ref.read(uploadProfileImageProvider).profileImagePath); | ||||
|           ref.watch(authenticationProvider.notifier).updateUserProfileImagePath( | ||||
|               ref.read(uploadProfileImageProvider).profileImagePath); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| @@ -117,7 +121,10 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|               DrawerHeader( | ||||
|                 decoration: const BoxDecoration( | ||||
|                   gradient: LinearGradient( | ||||
|                     colors: [Color.fromARGB(255, 216, 219, 238), Color.fromARGB(255, 226, 230, 231)], | ||||
|                     colors: [ | ||||
|                       Color.fromARGB(255, 216, 219, 238), | ||||
|                       Color.fromARGB(255, 226, 230, 231) | ||||
|                     ], | ||||
|                     begin: Alignment.centerRight, | ||||
|                     end: Alignment.centerLeft, | ||||
|                   ), | ||||
| @@ -155,7 +162,7 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|                       ], | ||||
|                     ), | ||||
|                     Text( | ||||
|                       "${_authState.firstName} ${_authState.lastName}", | ||||
|                       "${authState.firstName} ${authState.lastName}", | ||||
|                       style: TextStyle( | ||||
|                         color: Theme.of(context).primaryColor, | ||||
|                         fontWeight: FontWeight.bold, | ||||
| @@ -163,7 +170,7 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|                       ), | ||||
|                     ), | ||||
|                     Text( | ||||
|                       _authState.userEmail, | ||||
|                       authState.userEmail, | ||||
|                       style: TextStyle(color: Colors.grey[800], fontSize: 12), | ||||
|                     ) | ||||
|                   ], | ||||
| @@ -177,10 +184,14 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|                 ), | ||||
|                 title: const Text( | ||||
|                   "Sign Out", | ||||
|                   style: TextStyle(color: Colors.black54, fontSize: 14, fontWeight: FontWeight.bold), | ||||
|                   style: TextStyle( | ||||
|                       color: Colors.black54, | ||||
|                       fontSize: 14, | ||||
|                       fontWeight: FontWeight.bold), | ||||
|                 ), | ||||
|                 onTap: () async { | ||||
|                   bool res = await ref.read(authenticationProvider.notifier).logout(); | ||||
|                   bool res = | ||||
|                       await ref.read(authenticationProvider.notifier).logout(); | ||||
|  | ||||
|                   if (res) { | ||||
|                     ref.watch(backupProvider.notifier).cancelBackup(); | ||||
| @@ -206,19 +217,22 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|                 ), | ||||
|               ), | ||||
|               child: Padding( | ||||
|                 padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8), | ||||
|                 padding: | ||||
|                     const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8), | ||||
|                 child: Column( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                   children: [ | ||||
|                     Padding( | ||||
|                       padding: const EdgeInsets.all(8.0), | ||||
|                       child: Text( | ||||
|                         _serverInfoState.isVersionMismatch | ||||
|                             ? _serverInfoState.versionMismatchErrorMessage | ||||
|                         serverInfoState.isVersionMismatch | ||||
|                             ? serverInfoState.versionMismatchErrorMessage | ||||
|                             : "Client and Server are up-to-date", | ||||
|                         textAlign: TextAlign.center, | ||||
|                         style: | ||||
|                             TextStyle(fontSize: 11, color: Theme.of(context).primaryColor, fontWeight: FontWeight.w600), | ||||
|                         style: TextStyle( | ||||
|                             fontSize: 11, | ||||
|                             color: Theme.of(context).primaryColor, | ||||
|                             fontWeight: FontWeight.w600), | ||||
|                       ), | ||||
|                     ), | ||||
|                     const Divider(), | ||||
| @@ -256,7 +270,7 @@ class ProfileDrawer extends HookConsumerWidget { | ||||
|                           ), | ||||
|                         ), | ||||
|                         Text( | ||||
|                           "${_serverInfoState.serverVersion.major}.${_serverInfoState.serverVersion.minor}.${_serverInfoState.serverVersion.patch}", | ||||
|                           "${serverInfoState.serverVersion.major}.${serverInfoState.serverVersion.minor}.${serverInfoState.serverVersion.patch}", | ||||
|                           style: TextStyle( | ||||
|                             fontSize: 11, | ||||
|                             color: Colors.grey[500], | ||||
|   | ||||
| @@ -19,10 +19,11 @@ class HomePage extends HookConsumerWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     ScrollController _scrollController = useScrollController(); | ||||
|     ScrollController scrollController = useScrollController(); | ||||
|     var assetGroupByDateTime = ref.watch(assetGroupByDateTimeProvider); | ||||
|     List<Widget> _imageGridGroup = []; | ||||
|     var isMultiSelectEnable = ref.watch(homePageStateProvider).isMultiSelectEnable; | ||||
|     List<Widget> imageGridGroup = []; | ||||
|     var isMultiSelectEnable = | ||||
|         ref.watch(homePageStateProvider).isMultiSelectEnable; | ||||
|     var homePageState = ref.watch(homePageStateProvider); | ||||
|  | ||||
|     useEffect(() { | ||||
| @@ -39,7 +40,8 @@ class HomePage extends HookConsumerWidget { | ||||
|     _buildSelectedItemCountIndicator() { | ||||
|       return isMultiSelectEnable | ||||
|           ? DisableMultiSelectButton( | ||||
|               onPressed: ref.watch(homePageStateProvider.notifier).disableMultiSelect, | ||||
|               onPressed: | ||||
|                   ref.watch(homePageStateProvider.notifier).disableMultiSelect, | ||||
|               selectedItemCount: homePageState.selectedItems.length, | ||||
|             ) | ||||
|           : Container(); | ||||
| @@ -59,7 +61,7 @@ class HomePage extends HookConsumerWidget { | ||||
|  | ||||
|           if (lastMonth != null) { | ||||
|             if (currentMonth - lastMonth! != 0) { | ||||
|               _imageGridGroup.add( | ||||
|               imageGridGroup.add( | ||||
|                 MonthlyTitleText( | ||||
|                   isoDate: dateGroup, | ||||
|                 ), | ||||
| @@ -67,14 +69,14 @@ class HomePage extends HookConsumerWidget { | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           _imageGridGroup.add( | ||||
|           imageGridGroup.add( | ||||
|             DailyTitleText( | ||||
|               isoDate: dateGroup, | ||||
|               assetGroup: immichAssetList, | ||||
|             ), | ||||
|           ); | ||||
|  | ||||
|           _imageGridGroup.add( | ||||
|           imageGridGroup.add( | ||||
|             ImageGrid(assetGroup: immichAssetList), | ||||
|           ); | ||||
|  | ||||
| @@ -109,12 +111,12 @@ class HomePage extends HookConsumerWidget { | ||||
|               padding: const EdgeInsets.only(top: 50.0), | ||||
|               child: DraggableScrollbar.semicircle( | ||||
|                 backgroundColor: Theme.of(context).primaryColor, | ||||
|                 controller: _scrollController, | ||||
|                 controller: scrollController, | ||||
|                 heightScrollThumb: 48.0, | ||||
|                 child: CustomScrollView( | ||||
|                   controller: _scrollController, | ||||
|                   controller: scrollController, | ||||
|                   slivers: [ | ||||
|                     ..._imageGridGroup, | ||||
|                     ...imageGridGroup, | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|   | ||||
| @@ -45,20 +45,23 @@ class SearchPageStateNotifier extends StateNotifier<SearchPageState> { | ||||
|   } | ||||
|  | ||||
|   void getSuggestedSearchTerms() async { | ||||
|     var userSuggestedSearchTerms = await _searchService.getUserSuggestedSearchTerms(); | ||||
|     var userSuggestedSearchTerms = | ||||
|         await _searchService.getUserSuggestedSearchTerms(); | ||||
|  | ||||
|     state = state.copyWith(userSuggestedSearchTerms: userSuggestedSearchTerms); | ||||
|   } | ||||
| } | ||||
|  | ||||
| final searchPageStateProvider = StateNotifierProvider<SearchPageStateNotifier, SearchPageState>((ref) { | ||||
| final searchPageStateProvider = | ||||
|     StateNotifierProvider<SearchPageStateNotifier, SearchPageState>((ref) { | ||||
|   return SearchPageStateNotifier(); | ||||
| }); | ||||
|  | ||||
| final getCuratedLocationProvider = FutureProvider.autoDispose<List<CuratedLocation>>((ref) async { | ||||
|   final SearchService _searchService = SearchService(); | ||||
| final getCuratedLocationProvider = | ||||
|     FutureProvider.autoDispose<List<CuratedLocation>>((ref) async { | ||||
|   final SearchService searchService = SearchService(); | ||||
|  | ||||
|   var curatedLocation = await _searchService.getCuratedLocation(); | ||||
|   var curatedLocation = await searchService.getCuratedLocation(); | ||||
|   if (curatedLocation != null) { | ||||
|     return curatedLocation; | ||||
|   } else { | ||||
| @@ -66,10 +69,11 @@ final getCuratedLocationProvider = FutureProvider.autoDispose<List<CuratedLocati | ||||
|   } | ||||
| }); | ||||
|  | ||||
| final getCuratedObjectProvider = FutureProvider.autoDispose<List<CuratedObject>>((ref) async { | ||||
|   final SearchService _searchService = SearchService(); | ||||
| final getCuratedObjectProvider = | ||||
|     FutureProvider.autoDispose<List<CuratedObject>>((ref) async { | ||||
|   final SearchService searchService = SearchService(); | ||||
|  | ||||
|   var curatedObject = await _searchService.getCuratedObjects(); | ||||
|   var curatedObject = await searchService.getCuratedObjects(); | ||||
|   if (curatedObject != null) { | ||||
|     return curatedObject; | ||||
|   } else { | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:flutter_spinkit/flutter_spinkit.dart'; | ||||
| import 'package:hive_flutter/hive_flutter.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/constants/hive_box.dart'; | ||||
|   | ||||
| @@ -12,24 +12,27 @@ import 'package:immich_mobile/modules/search/providers/search_result_page.provid | ||||
| import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart'; | ||||
|  | ||||
| class SearchResultPage extends HookConsumerWidget { | ||||
|   SearchResultPage({Key? key, required this.searchTerm}) : super(key: key); | ||||
|   const SearchResultPage({Key? key, required this.searchTerm}) | ||||
|       : super(key: key); | ||||
|  | ||||
|   final String searchTerm; | ||||
|   late FocusNode searchFocusNode; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     ScrollController _scrollController = useScrollController(); | ||||
|     ScrollController scrollController = useScrollController(); | ||||
|     final searchTermController = useTextEditingController(text: ""); | ||||
|     final isNewSearch = useState(false); | ||||
|     final currentSearchTerm = useState(searchTerm); | ||||
|  | ||||
|     List<Widget> _imageGridGroup = []; | ||||
|     final List<Widget> imageGridGroup = []; | ||||
|  | ||||
|     late FocusNode searchFocusNode; | ||||
|  | ||||
|     useEffect(() { | ||||
|       searchFocusNode = FocusNode(); | ||||
|  | ||||
|       Future.delayed(Duration.zero, () => ref.read(searchResultPageProvider.notifier).search(searchTerm)); | ||||
|       Future.delayed(Duration.zero, | ||||
|           () => ref.read(searchResultPageProvider.notifier).search(searchTerm)); | ||||
|       return () => searchFocusNode.dispose(); | ||||
|     }, []); | ||||
|  | ||||
| @@ -85,7 +88,10 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|           children: [ | ||||
|             Text( | ||||
|               currentSearchTerm.value, | ||||
|               style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 13, fontWeight: FontWeight.bold), | ||||
|               style: TextStyle( | ||||
|                   color: Theme.of(context).primaryColor, | ||||
|                   fontSize: 13, | ||||
|                   fontWeight: FontWeight.bold), | ||||
|               maxLines: 1, | ||||
|             ), | ||||
|             Icon( | ||||
| @@ -124,7 +130,7 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|  | ||||
|             if (lastMonth != null) { | ||||
|               if (currentMonth - lastMonth! != 0) { | ||||
|                 _imageGridGroup.add( | ||||
|                 imageGridGroup.add( | ||||
|                   MonthlyTitleText( | ||||
|                     isoDate: dateGroup, | ||||
|                   ), | ||||
| @@ -132,14 +138,14 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|               } | ||||
|             } | ||||
|  | ||||
|             _imageGridGroup.add( | ||||
|             imageGridGroup.add( | ||||
|               DailyTitleText( | ||||
|                 isoDate: dateGroup, | ||||
|                 assetGroup: immichAssetList, | ||||
|               ), | ||||
|             ); | ||||
|  | ||||
|             _imageGridGroup.add( | ||||
|             imageGridGroup.add( | ||||
|               ImageGrid(assetGroup: immichAssetList), | ||||
|             ); | ||||
|  | ||||
| @@ -148,11 +154,11 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|  | ||||
|           return DraggableScrollbar.semicircle( | ||||
|             backgroundColor: Theme.of(context).primaryColor, | ||||
|             controller: _scrollController, | ||||
|             controller: scrollController, | ||||
|             heightScrollThumb: 48.0, | ||||
|             child: CustomScrollView( | ||||
|               controller: _scrollController, | ||||
|               slivers: [..._imageGridGroup], | ||||
|               controller: scrollController, | ||||
|               slivers: [...imageGridGroup], | ||||
|             ), | ||||
|           ); | ||||
|         } else { | ||||
| @@ -192,7 +198,9 @@ class SearchResultPage extends HookConsumerWidget { | ||||
|         child: Stack( | ||||
|           children: [ | ||||
|             _buildSearchResult(), | ||||
|             isNewSearch.value ? SearchSuggestionList(onSubmitted: _onSearchSubmitted) : Container(), | ||||
|             isNewSearch.value | ||||
|                 ? SearchSuggestionList(onSubmitted: _onSearchSubmitted) | ||||
|                 : Container(), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|   | ||||
| @@ -8,7 +8,8 @@ class SharedAlbumNotifier extends StateNotifier<List<SharedAlbum>> { | ||||
|   final SharedAlbumService _sharedAlbumService = SharedAlbumService(); | ||||
|  | ||||
|   getAllSharedAlbums() async { | ||||
|     List<SharedAlbum> sharedAlbums = await _sharedAlbumService.getAllSharedAlbum(); | ||||
|     List<SharedAlbum> sharedAlbums = | ||||
|         await _sharedAlbumService.getAllSharedAlbum(); | ||||
|  | ||||
|     state = sharedAlbums; | ||||
|   } | ||||
| @@ -35,7 +36,8 @@ class SharedAlbumNotifier extends StateNotifier<List<SharedAlbum>> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<bool> removeAssetFromAlbum(String albumId, List<String> assetIds) async { | ||||
|   Future<bool> removeAssetFromAlbum( | ||||
|       String albumId, List<String> assetIds) async { | ||||
|     var res = await _sharedAlbumService.removeAssetFromAlbum(albumId, assetIds); | ||||
|  | ||||
|     if (res) { | ||||
| @@ -46,12 +48,14 @@ class SharedAlbumNotifier extends StateNotifier<List<SharedAlbum>> { | ||||
|   } | ||||
| } | ||||
|  | ||||
| final sharedAlbumProvider = StateNotifierProvider<SharedAlbumNotifier, List<SharedAlbum>>((ref) { | ||||
| final sharedAlbumProvider = | ||||
|     StateNotifierProvider<SharedAlbumNotifier, List<SharedAlbum>>((ref) { | ||||
|   return SharedAlbumNotifier(); | ||||
| }); | ||||
|  | ||||
| final sharedAlbumDetailProvider = FutureProvider.autoDispose.family<SharedAlbum, String>((ref, albumId) async { | ||||
|   final SharedAlbumService _sharedAlbumService = SharedAlbumService(); | ||||
| final sharedAlbumDetailProvider = FutureProvider.autoDispose | ||||
|     .family<SharedAlbum, String>((ref, albumId) async { | ||||
|   final SharedAlbumService sharedAlbumService = SharedAlbumService(); | ||||
|  | ||||
|   return await _sharedAlbumService.getAlbumDetail(albumId); | ||||
|   return await sharedAlbumService.getAlbumDetail(albumId); | ||||
| }); | ||||
|   | ||||
| @@ -26,18 +26,22 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final isMultiSelectionEnable = ref.watch(assetSelectionProvider).isMultiselectEnable; | ||||
|     final selectedAssetsInAlbum = ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer; | ||||
|     final isMultiSelectionEnable = | ||||
|         ref.watch(assetSelectionProvider).isMultiselectEnable; | ||||
|     final selectedAssetsInAlbum = | ||||
|         ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer; | ||||
|     final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText; | ||||
|     final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum; | ||||
|  | ||||
|     void _onDeleteAlbumPressed(String albumId) async { | ||||
|       ImmichLoadingOverlayController.appLoader.show(); | ||||
|  | ||||
|       bool isSuccess = await ref.watch(sharedAlbumProvider.notifier).deleteAlbum(albumId); | ||||
|       bool isSuccess = | ||||
|           await ref.watch(sharedAlbumProvider.notifier).deleteAlbum(albumId); | ||||
|  | ||||
|       if (isSuccess) { | ||||
|         AutoRouter.of(context).navigate(const TabControllerRoute(children: [SharingRoute()])); | ||||
|         AutoRouter.of(context) | ||||
|             .navigate(const TabControllerRoute(children: [SharingRoute()])); | ||||
|       } else { | ||||
|         ImmichToast.show( | ||||
|           context: context, | ||||
| @@ -53,10 +57,12 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|     void _onLeaveAlbumPressed(String albumId) async { | ||||
|       ImmichLoadingOverlayController.appLoader.show(); | ||||
|  | ||||
|       bool isSuccess = await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(albumId); | ||||
|       bool isSuccess = | ||||
|           await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(albumId); | ||||
|  | ||||
|       if (isSuccess) { | ||||
|         AutoRouter.of(context).navigate(const TabControllerRoute(children: [SharingRoute()])); | ||||
|         AutoRouter.of(context) | ||||
|             .navigate(const TabControllerRoute(children: [SharingRoute()])); | ||||
|       } else { | ||||
|         Navigator.pop(context); | ||||
|         ImmichToast.show( | ||||
| @@ -73,10 +79,11 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|     void _onRemoveFromAlbumPressed(String albumId) async { | ||||
|       ImmichLoadingOverlayController.appLoader.show(); | ||||
|  | ||||
|       bool isSuccess = await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum( | ||||
|             albumId, | ||||
|             selectedAssetsInAlbum.map((a) => a.id).toList(), | ||||
|           ); | ||||
|       bool isSuccess = | ||||
|           await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum( | ||||
|                 albumId, | ||||
|                 selectedAssetsInAlbum.map((a) => a.id).toList(), | ||||
|               ); | ||||
|  | ||||
|       if (isSuccess) { | ||||
|         Navigator.pop(context); | ||||
| @@ -153,15 +160,18 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|     _buildLeadingButton() { | ||||
|       if (isMultiSelectionEnable) { | ||||
|         return IconButton( | ||||
|           onPressed: () => ref.watch(assetSelectionProvider.notifier).disableMultiselection(), | ||||
|           onPressed: () => ref | ||||
|               .watch(assetSelectionProvider.notifier) | ||||
|               .disableMultiselection(), | ||||
|           icon: const Icon(Icons.close_rounded), | ||||
|           splashRadius: 25, | ||||
|         ); | ||||
|       } else if (isEditAlbum) { | ||||
|         return IconButton( | ||||
|           onPressed: () async { | ||||
|             bool isSuccess = | ||||
|                 await ref.watch(albumViewerProvider.notifier).changeAlbumTitle(albumId, userId, newAlbumTitle); | ||||
|             bool isSuccess = await ref | ||||
|                 .watch(albumViewerProvider.notifier) | ||||
|                 .changeAlbumTitle(albumId, userId, newAlbumTitle); | ||||
|  | ||||
|             if (!isSuccess) { | ||||
|               ImmichToast.show( | ||||
| @@ -187,7 +197,9 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget { | ||||
|     return AppBar( | ||||
|       elevation: 0, | ||||
|       leading: _buildLeadingButton(), | ||||
|       title: isMultiSelectionEnable ? Text(selectedAssetsInAlbum.length.toString()) : Container(), | ||||
|       title: isMultiSelectionEnable | ||||
|           ? Text(selectedAssetsInAlbum.length.toString()) | ||||
|           : Container(), | ||||
|       centerTitle: false, | ||||
|       actions: [ | ||||
|         IconButton( | ||||
|   | ||||
| @@ -28,8 +28,9 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     FocusNode titleFocusNode = useFocusNode(); | ||||
|     ScrollController _scrollController = useScrollController(); | ||||
|     AsyncValue<SharedAlbum> _albumInfo = ref.watch(sharedAlbumDetailProvider(albumId)); | ||||
|     ScrollController scrollController = useScrollController(); | ||||
|     AsyncValue<SharedAlbum> albumInfo = | ||||
|         ref.watch(sharedAlbumDetailProvider(albumId)); | ||||
|  | ||||
|     final userId = ref.watch(authenticationProvider).userId; | ||||
|  | ||||
| @@ -44,16 +45,16 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|  | ||||
|       ref.watch(assetSelectionProvider.notifier).setIsAlbumExist(true); | ||||
|  | ||||
|       AssetSelectionPageResult? returnPayload = | ||||
|           await AutoRouter.of(context).push<AssetSelectionPageResult?>(const AssetSelectionRoute()); | ||||
|       AssetSelectionPageResult? returnPayload = await AutoRouter.of(context) | ||||
|           .push<AssetSelectionPageResult?>(const AssetSelectionRoute()); | ||||
|  | ||||
|       if (returnPayload != null) { | ||||
|         // Check if there is new assets add | ||||
|         if (returnPayload.selectedAdditionalAsset.isNotEmpty) { | ||||
|           ImmichLoadingOverlayController.appLoader.show(); | ||||
|  | ||||
|           var isSuccess = | ||||
|               await SharedAlbumService().addAdditionalAssetToAlbum(returnPayload.selectedAdditionalAsset, albumId); | ||||
|           var isSuccess = await SharedAlbumService().addAdditionalAssetToAlbum( | ||||
|               returnPayload.selectedAdditionalAsset, albumId); | ||||
|  | ||||
|           if (isSuccess) { | ||||
|             ref.refresh(sharedAlbumDetailProvider(albumId)); | ||||
| @@ -69,13 +70,15 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     void _onAddUsersPressed(SharedAlbum albumInfo) async { | ||||
|       List<String>? sharedUserIds = | ||||
|           await AutoRouter.of(context).push<List<String>?>(SelectAdditionalUserForSharingRoute(albumInfo: albumInfo)); | ||||
|       List<String>? sharedUserIds = await AutoRouter.of(context) | ||||
|           .push<List<String>?>( | ||||
|               SelectAdditionalUserForSharingRoute(albumInfo: albumInfo)); | ||||
|  | ||||
|       if (sharedUserIds != null) { | ||||
|         ImmichLoadingOverlayController.appLoader.show(); | ||||
|  | ||||
|         var isSuccess = await SharedAlbumService().addAdditionalUserToAlbum(sharedUserIds, albumId); | ||||
|         var isSuccess = await SharedAlbumService() | ||||
|             .addAdditionalUserToAlbum(sharedUserIds, albumId); | ||||
|  | ||||
|         if (isSuccess) { | ||||
|           ref.refresh(sharedAlbumDetailProvider(albumId)); | ||||
| @@ -95,7 +98,9 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|               ) | ||||
|             : Padding( | ||||
|                 padding: const EdgeInsets.only(left: 8.0), | ||||
|                 child: Text(albumInfo.albumName, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)), | ||||
|                 child: Text(albumInfo.albumName, | ||||
|                     style: const TextStyle( | ||||
|                         fontSize: 24, fontWeight: FontWeight.bold)), | ||||
|               ), | ||||
|       ); | ||||
|     } | ||||
| @@ -103,8 +108,10 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|     Widget _buildAlbumDateRange(SharedAlbum albumInfo) { | ||||
|       if (albumInfo.assets != null && albumInfo.assets!.isNotEmpty) { | ||||
|         String startDate = ""; | ||||
|         DateTime parsedStartDate = DateTime.parse(albumInfo.assets!.first.createdAt); | ||||
|         DateTime parsedEndDate = DateTime.parse(albumInfo.assets!.last.createdAt); | ||||
|         DateTime parsedStartDate = | ||||
|             DateTime.parse(albumInfo.assets!.first.createdAt); | ||||
|         DateTime parsedEndDate = | ||||
|             DateTime.parse(albumInfo.assets!.last.createdAt); | ||||
|  | ||||
|         if (parsedStartDate.year == parsedEndDate.year) { | ||||
|           startDate = DateFormat('LLL d').format(parsedStartDate); | ||||
| @@ -118,7 +125,8 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|           padding: const EdgeInsets.only(left: 16.0, top: 8), | ||||
|           child: Text( | ||||
|             "$startDate-$endDate", | ||||
|             style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.grey), | ||||
|             style: const TextStyle( | ||||
|                 fontSize: 14, fontWeight: FontWeight.bold, color: Colors.grey), | ||||
|           ), | ||||
|         ); | ||||
|       } else { | ||||
| @@ -147,8 +155,9 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|                       child: Padding( | ||||
|                         padding: const EdgeInsets.all(2.0), | ||||
|                         child: ClipRRect( | ||||
|                           child: Image.asset('assets/immich-logo-no-outline.png'), | ||||
|                           borderRadius: BorderRadius.circular(50.0), | ||||
|                           child: | ||||
|                               Image.asset('assets/immich-logo-no-outline.png'), | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
| @@ -217,10 +226,10 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|         }, | ||||
|         child: DraggableScrollbar.semicircle( | ||||
|           backgroundColor: Theme.of(context).primaryColor, | ||||
|           controller: _scrollController, | ||||
|           controller: scrollController, | ||||
|           heightScrollThumb: 48.0, | ||||
|           child: CustomScrollView( | ||||
|             controller: _scrollController, | ||||
|             controller: scrollController, | ||||
|             slivers: [ | ||||
|               _buildHeader(albumInfo), | ||||
|               SliverPersistentHeader( | ||||
| @@ -242,8 +251,9 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     return Scaffold( | ||||
|       appBar: AlbumViewerAppbar(albumInfo: _albumInfo, userId: userId, albumId: albumId), | ||||
|       body: _albumInfo.when( | ||||
|       appBar: AlbumViewerAppbar( | ||||
|           albumInfo: albumInfo, userId: userId, albumId: albumId), | ||||
|       body: albumInfo.when( | ||||
|         data: (albumInfo) => _buildBody(albumInfo), | ||||
|         error: (e, _) => Center(child: Text("Error loading album info $e")), | ||||
|         loading: () => const Center( | ||||
|   | ||||
| @@ -13,13 +13,15 @@ class AssetSelectionPage extends HookConsumerWidget { | ||||
|   const AssetSelectionPage({Key? key}) : super(key: key); | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     ScrollController _scrollController = useScrollController(); | ||||
|     ScrollController scrollController = useScrollController(); | ||||
|     var assetGroupMonthYear = ref.watch(assetGroupByMonthYearProvider); | ||||
|     final selectedAssets = ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum; | ||||
|     final newAssetsForAlbum = ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum; | ||||
|     final selectedAssets = | ||||
|         ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum; | ||||
|     final newAssetsForAlbum = | ||||
|         ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum; | ||||
|     final isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist; | ||||
|  | ||||
|     List<Widget> _imageGridGroup = []; | ||||
|     List<Widget> imageGridGroup = []; | ||||
|  | ||||
|     String _buildAssetCountText() { | ||||
|       if (isAlbumExist) { | ||||
| @@ -31,19 +33,20 @@ class AssetSelectionPage extends HookConsumerWidget { | ||||
|  | ||||
|     Widget _buildBody() { | ||||
|       assetGroupMonthYear.forEach((monthYear, assetGroup) { | ||||
|         _imageGridGroup.add(MonthGroupTitle(month: monthYear, assetGroup: assetGroup)); | ||||
|         _imageGridGroup.add(AssetGridByMonth(assetGroup: assetGroup)); | ||||
|         imageGridGroup | ||||
|             .add(MonthGroupTitle(month: monthYear, assetGroup: assetGroup)); | ||||
|         imageGridGroup.add(AssetGridByMonth(assetGroup: assetGroup)); | ||||
|       }); | ||||
|  | ||||
|       return Stack( | ||||
|         children: [ | ||||
|           DraggableScrollbar.semicircle( | ||||
|             backgroundColor: Theme.of(context).primaryColor, | ||||
|             controller: _scrollController, | ||||
|             controller: scrollController, | ||||
|             heightScrollThumb: 48.0, | ||||
|             child: CustomScrollView( | ||||
|               controller: _scrollController, | ||||
|               slivers: [..._imageGridGroup], | ||||
|               controller: scrollController, | ||||
|               slivers: [...imageGridGroup], | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
| @@ -71,7 +74,8 @@ class AssetSelectionPage extends HookConsumerWidget { | ||||
|               ), | ||||
|         centerTitle: false, | ||||
|         actions: [ | ||||
|           (!isAlbumExist && selectedAssets.isNotEmpty) || (isAlbumExist && newAssetsForAlbum.isNotEmpty) | ||||
|           (!isAlbumExist && selectedAssets.isNotEmpty) || | ||||
|                   (isAlbumExist && newAssetsForAlbum.isNotEmpty) | ||||
|               ? TextButton( | ||||
|                   onPressed: () { | ||||
|                     var payload = AssetSelectionPageResult( | ||||
|   | ||||
| @@ -15,11 +15,13 @@ class CreateSharedAlbumPage extends HookConsumerWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final albumTitleController = useTextEditingController.fromValue(TextEditingValue.empty); | ||||
|     final albumTitleController = | ||||
|         useTextEditingController.fromValue(TextEditingValue.empty); | ||||
|     final albumTitleTextFieldFocusNode = useFocusNode(); | ||||
|     final isAlbumTitleTextFieldFocus = useState(false); | ||||
|     final isAlbumTitleEmpty = useState(true); | ||||
|     final selectedAssets = ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum; | ||||
|     final selectedAssets = | ||||
|         ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum; | ||||
|  | ||||
|     _showSelectUserPage() { | ||||
|       AutoRouter.of(context).push(const SelectUserForSharingRoute()); | ||||
| @@ -38,8 +40,8 @@ class CreateSharedAlbumPage extends HookConsumerWidget { | ||||
|     _onSelectPhotosButtonPressed() async { | ||||
|       ref.watch(assetSelectionProvider.notifier).setIsAlbumExist(false); | ||||
|  | ||||
|       AssetSelectionPageResult? selectedAsset = | ||||
|           await AutoRouter.of(context).push<AssetSelectionPageResult?>(const AssetSelectionRoute()); | ||||
|       AssetSelectionPageResult? selectedAsset = await AutoRouter.of(context) | ||||
|           .push<AssetSelectionPageResult?>(const AssetSelectionRoute()); | ||||
|  | ||||
|       if (selectedAsset == null) { | ||||
|         ref.watch(assetSelectionProvider.notifier).removeAll(); | ||||
| @@ -84,16 +86,22 @@ class CreateSharedAlbumPage extends HookConsumerWidget { | ||||
|             child: OutlinedButton.icon( | ||||
|               style: OutlinedButton.styleFrom( | ||||
|                   alignment: Alignment.centerLeft, | ||||
|                   padding: const EdgeInsets.symmetric(vertical: 22, horizontal: 16), | ||||
|                   side: const BorderSide(color: Color.fromARGB(255, 206, 206, 206)), | ||||
|                   shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5))), | ||||
|                   padding: | ||||
|                       const EdgeInsets.symmetric(vertical: 22, horizontal: 16), | ||||
|                   side: const BorderSide( | ||||
|                       color: Color.fromARGB(255, 206, 206, 206)), | ||||
|                   shape: RoundedRectangleBorder( | ||||
|                       borderRadius: BorderRadius.circular(5))), | ||||
|               onPressed: _onSelectPhotosButtonPressed, | ||||
|               icon: const Icon(Icons.add_rounded), | ||||
|               label: Padding( | ||||
|                 padding: const EdgeInsets.only(left: 8.0), | ||||
|                 child: Text( | ||||
|                   'Select Photos', | ||||
|                   style: TextStyle(fontSize: 16, color: Colors.grey[700], fontWeight: FontWeight.bold), | ||||
|                   style: TextStyle( | ||||
|                       fontSize: 16, | ||||
|                       color: Colors.grey[700], | ||||
|                       fontWeight: FontWeight.bold), | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
| @@ -141,7 +149,8 @@ class CreateSharedAlbumPage extends HookConsumerWidget { | ||||
|               (BuildContext context, int index) { | ||||
|                 return GestureDetector( | ||||
|                   onTap: _onBackgroundTapped, | ||||
|                   child: SharedAlbumThumbnailImage(asset: selectedAssets.toList()[index]), | ||||
|                   child: SharedAlbumThumbnailImage( | ||||
|                       asset: selectedAssets.toList()[index]), | ||||
|                 ); | ||||
|               }, | ||||
|               childCount: selectedAssets.length, | ||||
| @@ -169,7 +178,9 @@ class CreateSharedAlbumPage extends HookConsumerWidget { | ||||
|           ), | ||||
|           actions: [ | ||||
|             TextButton( | ||||
|               onPressed: albumTitleController.text.isNotEmpty ? _showSelectUserPage : null, | ||||
|               onPressed: albumTitleController.text.isNotEmpty | ||||
|                   ? _showSelectUserPage | ||||
|                   : null, | ||||
|               child: const Text( | ||||
|                 'Share', | ||||
|                 style: TextStyle( | ||||
| @@ -189,13 +200,13 @@ class CreateSharedAlbumPage extends HookConsumerWidget { | ||||
|                 pinned: true, | ||||
|                 floating: false, | ||||
|                 bottom: PreferredSize( | ||||
|                   preferredSize: const Size.fromHeight(66.0), | ||||
|                   child: Column( | ||||
|                     children: [ | ||||
|                       _buildTitleInputField(), | ||||
|                       _buildControlButton(), | ||||
|                     ], | ||||
|                   ), | ||||
|                   preferredSize: const Size.fromHeight(66.0), | ||||
|                 ), | ||||
|               ), | ||||
|               _buildTitle(), | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import 'dart:convert'; | ||||
| import 'dart:ffi'; | ||||
|  | ||||
| class DeviceInfoRemote { | ||||
|   final int id; | ||||
| @@ -66,7 +65,8 @@ class DeviceInfoRemote { | ||||
|  | ||||
|   String toJson() => json.encode(toMap()); | ||||
|  | ||||
|   factory DeviceInfoRemote.fromJson(String source) => DeviceInfoRemote.fromMap(json.decode(source)); | ||||
|   factory DeviceInfoRemote.fromJson(String source) => | ||||
|       DeviceInfoRemote.fromMap(json.decode(source)); | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|   | ||||
| @@ -46,37 +46,45 @@ class AssetNotifier extends StateNotifier<List<ImmichAsset>> { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     final List<String> result = await PhotoManager.editor.deleteWithIds(deleteIdList); | ||||
|     // final List<String> result = await PhotoManager.editor.deleteWithIds(deleteIdList); | ||||
|     await PhotoManager.editor.deleteWithIds(deleteIdList); | ||||
|  | ||||
|     // Delete asset on server | ||||
|     List<DeleteAssetResponse>? deleteAssetResult = await _assetService.deleteAssets(deleteAssets); | ||||
|     List<DeleteAssetResponse>? deleteAssetResult = | ||||
|         await _assetService.deleteAssets(deleteAssets); | ||||
|     if (deleteAssetResult == null) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     for (var asset in deleteAssetResult) { | ||||
|       if (asset.status == 'success') { | ||||
|         state = state.where((immichAsset) => immichAsset.id != asset.id).toList(); | ||||
|         state = | ||||
|             state.where((immichAsset) => immichAsset.id != asset.id).toList(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| final assetProvider = StateNotifierProvider<AssetNotifier, List<ImmichAsset>>((ref) { | ||||
| final assetProvider = | ||||
|     StateNotifierProvider<AssetNotifier, List<ImmichAsset>>((ref) { | ||||
|   return AssetNotifier(ref); | ||||
| }); | ||||
|  | ||||
| final assetGroupByDateTimeProvider = StateProvider((ref) { | ||||
|   var assets = ref.watch(assetProvider); | ||||
|  | ||||
|   assets.sortByCompare<DateTime>((e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a)); | ||||
|   return assets.groupListsBy((element) => DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt))); | ||||
|   assets.sortByCompare<DateTime>( | ||||
|       (e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a)); | ||||
|   return assets.groupListsBy((element) => | ||||
|       DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt))); | ||||
| }); | ||||
|  | ||||
| final assetGroupByMonthYearProvider = StateProvider((ref) { | ||||
|   var assets = ref.watch(assetProvider); | ||||
|  | ||||
|   assets.sortByCompare<DateTime>((e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a)); | ||||
|   assets.sortByCompare<DateTime>( | ||||
|       (e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a)); | ||||
|  | ||||
|   return assets.groupListsBy((element) => DateFormat('MMMM, y').format(DateTime.parse(element.createdAt))); | ||||
|   return assets.groupListsBy((element) => | ||||
|       DateFormat('MMMM, y').format(DateTime.parse(element.createdAt))); | ||||
| }); | ||||
|   | ||||
| @@ -34,7 +34,8 @@ class ReleaseInfoNotifier extends StateNotifier<String> { | ||||
|           return; | ||||
|         } | ||||
|  | ||||
|         if (latestTagVersion.isNotEmpty && localReleaseVersion != latestTagVersion) { | ||||
|         if (latestTagVersion.isNotEmpty && | ||||
|             localReleaseVersion != latestTagVersion) { | ||||
|           VersionAnnouncementOverlayController.appLoader.show(); | ||||
|           return; | ||||
|         } | ||||
| @@ -54,4 +55,5 @@ class ReleaseInfoNotifier extends StateNotifier<String> { | ||||
|   } | ||||
| } | ||||
|  | ||||
| final releaseInfoProvider = StateNotifierProvider<ReleaseInfoNotifier, String>((ref) => ReleaseInfoNotifier()); | ||||
| final releaseInfoProvider = StateNotifierProvider<ReleaseInfoNotifier, String>( | ||||
|     (ref) => ReleaseInfoNotifier()); | ||||
|   | ||||
| @@ -3,12 +3,11 @@ import 'dart:convert'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:hive/hive.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/shared/providers/asset.provider.dart'; | ||||
| import 'package:immich_mobile/shared/models/immich_asset.model.dart'; | ||||
| import 'package:socket_io_client/socket_io_client.dart'; | ||||
|  | ||||
| import 'package:immich_mobile/constants/hive_box.dart'; | ||||
| import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | ||||
| import 'package:immich_mobile/shared/models/immich_asset.model.dart'; | ||||
| import 'package:immich_mobile/shared/providers/asset.provider.dart'; | ||||
| import 'package:socket_io_client/socket_io_client.dart'; | ||||
|  | ||||
| class WebscoketState { | ||||
|   final Socket? socket; | ||||
| @@ -30,13 +29,16 @@ class WebscoketState { | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String toString() => 'WebscoketState(socket: $socket, isConnected: $isConnected)'; | ||||
|   String toString() => | ||||
|       'WebscoketState(socket: $socket, isConnected: $isConnected)'; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     if (identical(this, other)) return true; | ||||
|  | ||||
|     return other is WebscoketState && other.socket == socket && other.isConnected == isConnected; | ||||
|     return other is WebscoketState && | ||||
|         other.socket == socket && | ||||
|         other.isConnected == isConnected; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
| @@ -44,7 +46,8 @@ class WebscoketState { | ||||
| } | ||||
|  | ||||
| class WebsocketNotifier extends StateNotifier<WebscoketState> { | ||||
|   WebsocketNotifier(this.ref) : super(WebscoketState(socket: null, isConnected: false)) { | ||||
|   WebsocketNotifier(this.ref) | ||||
|       : super(WebscoketState(socket: null, isConnected: false)) { | ||||
|     debugPrint("Init websocket instance"); | ||||
|   } | ||||
|  | ||||
| @@ -59,6 +62,7 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> { | ||||
|       try { | ||||
|         debugPrint("[WEBSOCKET] Attempting to connect to ws"); | ||||
|         // Configure socket transports must be sepecified | ||||
|  | ||||
|         Socket socket = io( | ||||
|           endpoint, | ||||
|           OptionBuilder() | ||||
| @@ -122,6 +126,7 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> { | ||||
|   } | ||||
| } | ||||
|  | ||||
| final websocketProvider = StateNotifierProvider<WebsocketNotifier, WebscoketState>((ref) { | ||||
| final websocketProvider = | ||||
|     StateNotifierProvider<WebsocketNotifier, WebscoketState>((ref) { | ||||
|   return WebsocketNotifier(ref); | ||||
| }); | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| import 'package:dio/dio.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:immich_mobile/shared/models/mapbox_info.model.dart'; | ||||
| import 'package:immich_mobile/shared/models/server_info.model.dart'; | ||||
| import 'package:immich_mobile/shared/models/server_version.model.dart'; | ||||
| import 'package:immich_mobile/shared/services/network.service.dart'; | ||||
| import 'package:immich_mobile/shared/models/server_info.model.dart'; | ||||
|  | ||||
| class ServerInfoService { | ||||
|   final NetworkService _networkService = NetworkService(); | ||||
|   | ||||
| @@ -12,8 +12,9 @@ class VersionAnnouncementOverlay extends HookConsumerWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     void goToReleaseNote() async { | ||||
|       final Uri _url = Uri.parse('https://github.com/alextran1502/immich/releases/latest'); | ||||
|       await launchUrl(_url); | ||||
|       final Uri url = | ||||
|           Uri.parse('https://github.com/alextran1502/immich/releases/latest'); | ||||
|       await launchUrl(url); | ||||
|     } | ||||
|  | ||||
|     void onAcknowledgeTapped() { | ||||
| @@ -21,7 +22,8 @@ class VersionAnnouncementOverlay extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     return ValueListenableBuilder<bool>( | ||||
|       valueListenable: VersionAnnouncementOverlayController.appLoader.loaderShowingNotifier, | ||||
|       valueListenable: | ||||
|           VersionAnnouncementOverlayController.appLoader.loaderShowingNotifier, | ||||
|       builder: (context, shouldShow, child) { | ||||
|         if (shouldShow) { | ||||
|           return Scaffold( | ||||
| @@ -51,10 +53,14 @@ class VersionAnnouncementOverlay extends HookConsumerWidget { | ||||
|                               child: RichText( | ||||
|                                 text: TextSpan( | ||||
|                                   style: const TextStyle( | ||||
|                                       fontSize: 14, fontFamily: 'WorkSans', color: Colors.black87, height: 1.2), | ||||
|                                       fontSize: 14, | ||||
|                                       fontFamily: 'WorkSans', | ||||
|                                       color: Colors.black87, | ||||
|                                       height: 1.2), | ||||
|                                   children: <TextSpan>[ | ||||
|                                     const TextSpan( | ||||
|                                       text: 'Hi friend, there is a new release of', | ||||
|                                       text: | ||||
|                                           'Hi friend, there is a new release of', | ||||
|                                     ), | ||||
|                                     const TextSpan( | ||||
|                                       text: ' Immich ', | ||||
| @@ -65,14 +71,16 @@ class VersionAnnouncementOverlay extends HookConsumerWidget { | ||||
|                                       ), | ||||
|                                     ), | ||||
|                                     const TextSpan( | ||||
|                                       text: "please take your time to visit the ", | ||||
|                                       text: | ||||
|                                           "please take your time to visit the ", | ||||
|                                     ), | ||||
|                                     TextSpan( | ||||
|                                       text: "release note", | ||||
|                                       style: const TextStyle( | ||||
|                                         decoration: TextDecoration.underline, | ||||
|                                       ), | ||||
|                                       recognizer: TapGestureRecognizer()..onTap = goToReleaseNote, | ||||
|                                       recognizer: TapGestureRecognizer() | ||||
|                                         ..onTap = goToReleaseNote, | ||||
|                                     ), | ||||
|                                     const TextSpan( | ||||
|                                       text: | ||||
| @@ -91,7 +99,8 @@ class VersionAnnouncementOverlay extends HookConsumerWidget { | ||||
|                                     primary: Colors.indigo, | ||||
|                                     onPrimary: Colors.grey[50], | ||||
|                                     elevation: 2, | ||||
|                                     padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25), | ||||
|                                     padding: const EdgeInsets.symmetric( | ||||
|                                         vertical: 10, horizontal: 25), | ||||
|                                   ), | ||||
|                                   onPressed: onAcknowledgeTapped, | ||||
|                                   child: const Text( | ||||
| @@ -119,7 +128,8 @@ class VersionAnnouncementOverlay extends HookConsumerWidget { | ||||
| } | ||||
|  | ||||
| class VersionAnnouncementOverlayController { | ||||
|   static final VersionAnnouncementOverlayController appLoader = VersionAnnouncementOverlayController(); | ||||
|   static final VersionAnnouncementOverlayController appLoader = | ||||
|       VersionAnnouncementOverlayController(); | ||||
|   ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false); | ||||
|   ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message'); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import 'package:dio/dio.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hive_flutter/hive_flutter.dart'; | ||||
| import 'package:immich_mobile/constants/hive_box.dart'; | ||||
|  | ||||
|   | ||||
| @@ -198,7 +198,7 @@ packages: | ||||
|     source: hosted | ||||
|     version: "4.1.0" | ||||
|   collection: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: collection | ||||
|       url: "https://pub.dartlang.org" | ||||
| @@ -233,7 +233,7 @@ packages: | ||||
|     source: hosted | ||||
|     version: "0.17.1" | ||||
|   cupertino_icons: | ||||
|     dependency: "direct main" | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: cupertino_icons | ||||
|       url: "https://pub.dartlang.org" | ||||
| @@ -334,7 +334,7 @@ packages: | ||||
|       name: flutter_lints | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.4" | ||||
|     version: "2.0.1" | ||||
|   flutter_map: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -465,12 +465,12 @@ packages: | ||||
|     source: hosted | ||||
|     version: "3.2.0" | ||||
|   http_parser: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: http_parser | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "4.0.0" | ||||
|     version: "4.0.1" | ||||
|   image: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -542,7 +542,7 @@ packages: | ||||
|     source: hosted | ||||
|     version: "4.5.0" | ||||
|   latlong2: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: latlong2 | ||||
|       url: "https://pub.dartlang.org" | ||||
| @@ -554,7 +554,7 @@ packages: | ||||
|       name: lints | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.1" | ||||
|     version: "2.0.0" | ||||
|   lists: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -668,19 +668,19 @@ packages: | ||||
|     source: hosted | ||||
|     version: "1.0.5" | ||||
|   path: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: path | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.8.1" | ||||
|   path_provider: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: path_provider | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.10" | ||||
|     version: "2.0.11" | ||||
|   path_provider_android: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -861,13 +861,6 @@ packages: | ||||
|     description: flutter | ||||
|     source: sdk | ||||
|     version: "0.0.99" | ||||
|   sliver_tools: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: sliver_tools | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.2.6" | ||||
|   socket_io_client: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -1141,13 +1134,6 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.10" | ||||
|   visibility_detector: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: visibility_detector | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.2.2" | ||||
|   wakelock: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -10,7 +10,7 @@ environment: | ||||
| dependencies: | ||||
|   flutter: | ||||
|     sdk: flutter | ||||
|   cupertino_icons: ^1.0.2 | ||||
|  | ||||
|   photo_manager: ^2.0.6 | ||||
|   flutter_hooks: ^0.18.0 | ||||
|   hooks_riverpod: ^2.0.0-dev.0 | ||||
| @@ -23,12 +23,10 @@ dependencies: | ||||
|   auto_route: ^4.0.1 | ||||
|   exif: ^3.1.1 | ||||
|   transparent_image: ^2.0.0 | ||||
|   visibility_detector: ^0.2.2 | ||||
|   flutter_launcher_icons: "^0.9.2" | ||||
|   fluttertoast: ^8.0.8 | ||||
|   video_player: ^2.2.18 | ||||
|   chewie: ^1.2.2 | ||||
|   sliver_tools: ^0.2.5 | ||||
|   badges: ^2.0.2 | ||||
|   photo_view: ^0.14.0 | ||||
|   socket_io_client: ^2.0.0-beta.4-nullsafety.0 | ||||
| @@ -43,10 +41,17 @@ dependencies: | ||||
|   http: 0.13.4 | ||||
|   cancellation_token_http: ^1.1.0 | ||||
|  | ||||
|   path: ^1.8.1 | ||||
|   path_provider: ^2.0.11 | ||||
|   latlong2: ^0.8.1 | ||||
|   collection: ^1.16.0 | ||||
|   http_parser: ^4.0.1 | ||||
|  | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|     sdk: flutter | ||||
|   flutter_lints: ^1.0.0 | ||||
|   flutter_lints: ^2.0.1 | ||||
|   hive_generator: ^1.1.2 | ||||
|   build_runner: ^2.1.7 | ||||
|   auto_route_generator: ^4.0.0 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user