mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:39:37 -05:00 
			
		
		
		
	fix(mobile): asset description is not shown on the sheet when opened for the first time (#10377)
* fix: invalidate asset's description when asset details changed * refactor(exif-sheet): use description from exif instead * refactor(asset-description): remove asset_description.provider * fix(asset-description): set is empty based on exifInfo.description * chore: rename service to provider
This commit is contained in:
		
							parent
							
								
									7ce87abc95
								
							
						
					
					
						commit
						29e4666dfa
					
				@ -1,87 +0,0 @@
 | 
				
			|||||||
import 'dart:async';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/services/asset_description.service.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/entities/asset.entity.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/entities/exif_info.entity.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/providers/db.provider.dart';
 | 
					 | 
				
			||||||
import 'package:isar/isar.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class AssetDescriptionNotifier extends StateNotifier<String> {
 | 
					 | 
				
			||||||
  final Isar _db;
 | 
					 | 
				
			||||||
  final AssetDescriptionService _service;
 | 
					 | 
				
			||||||
  final Asset _asset;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  AssetDescriptionNotifier(
 | 
					 | 
				
			||||||
    this._db,
 | 
					 | 
				
			||||||
    this._service,
 | 
					 | 
				
			||||||
    this._asset,
 | 
					 | 
				
			||||||
  ) : super('') {
 | 
					 | 
				
			||||||
    _fetchLocalDescription();
 | 
					 | 
				
			||||||
    _fetchRemoteDescription();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  String get description => state;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Fetches the local database value for description
 | 
					 | 
				
			||||||
  /// and writes it to [state]
 | 
					 | 
				
			||||||
  void _fetchLocalDescription() async {
 | 
					 | 
				
			||||||
    final localExifId = _asset.exifInfo?.id;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Guard [localExifId] null
 | 
					 | 
				
			||||||
    if (localExifId == null) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Subscribe to local changes
 | 
					 | 
				
			||||||
    final exifInfo = await _db.exifInfos.get(localExifId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Guard
 | 
					 | 
				
			||||||
    if (exifInfo?.description == null) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    state = exifInfo!.description!;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Fetches the remote value and sets the state
 | 
					 | 
				
			||||||
  void _fetchRemoteDescription() async {
 | 
					 | 
				
			||||||
    final remoteAssetId = _asset.remoteId;
 | 
					 | 
				
			||||||
    final localExifId = _asset.exifInfo?.id;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Guard [remoteAssetId] and [localExifId] null
 | 
					 | 
				
			||||||
    if (remoteAssetId == null || localExifId == null) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Reads the latest from the remote and writes it to DB in the service
 | 
					 | 
				
			||||||
    final latest = await _service.readLatest(remoteAssetId, localExifId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    state = latest;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Sets the description to [description]
 | 
					 | 
				
			||||||
  /// Uses the service to set the asset value
 | 
					 | 
				
			||||||
  Future<void> setDescription(String description) async {
 | 
					 | 
				
			||||||
    state = description;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    final remoteAssetId = _asset.remoteId;
 | 
					 | 
				
			||||||
    final localExifId = _asset.exifInfo?.id;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Guard [remoteAssetId] and [localExifId] null
 | 
					 | 
				
			||||||
    if (remoteAssetId == null || localExifId == null) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return _service.setDescription(description, remoteAssetId, localExifId);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
final assetDescriptionProvider = StateNotifierProvider.autoDispose
 | 
					 | 
				
			||||||
    .family<AssetDescriptionNotifier, String, Asset>(
 | 
					 | 
				
			||||||
  (ref, asset) => AssetDescriptionNotifier(
 | 
					 | 
				
			||||||
    ref.watch(dbProvider),
 | 
					 | 
				
			||||||
    ref.watch(assetDescriptionServiceProvider),
 | 
					 | 
				
			||||||
    asset,
 | 
					 | 
				
			||||||
  ),
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/entities/asset.entity.dart';
 | 
				
			||||||
import 'package:immich_mobile/entities/exif_info.entity.dart';
 | 
					import 'package:immich_mobile/entities/exif_info.entity.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/api.provider.dart';
 | 
					import 'package:immich_mobile/providers/api.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/db.provider.dart';
 | 
					import 'package:immich_mobile/providers/db.provider.dart';
 | 
				
			||||||
@ -12,46 +13,36 @@ class AssetDescriptionService {
 | 
				
			|||||||
  final Isar _db;
 | 
					  final Isar _db;
 | 
				
			||||||
  final ApiService _api;
 | 
					  final ApiService _api;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setDescription(
 | 
					  Future<void> setDescription(
 | 
				
			||||||
    String description,
 | 
					    Asset asset,
 | 
				
			||||||
    String remoteAssetId,
 | 
					    String newDescription,
 | 
				
			||||||
    int localExifId,
 | 
					 | 
				
			||||||
  ) async {
 | 
					  ) async {
 | 
				
			||||||
 | 
					    final remoteAssetId = asset.remoteId;
 | 
				
			||||||
 | 
					    final localExifId = asset.exifInfo?.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Guard [remoteAssetId] and [localExifId] null
 | 
				
			||||||
 | 
					    if (remoteAssetId == null || localExifId == null) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final result = await _api.assetsApi.updateAsset(
 | 
					    final result = await _api.assetsApi.updateAsset(
 | 
				
			||||||
      remoteAssetId,
 | 
					      remoteAssetId,
 | 
				
			||||||
      UpdateAssetDto(description: description),
 | 
					      UpdateAssetDto(description: newDescription),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (result?.exifInfo?.description != null) {
 | 
					    final description = result?.exifInfo?.description;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (description != null) {
 | 
				
			||||||
      var exifInfo = await _db.exifInfos.get(localExifId);
 | 
					      var exifInfo = await _db.exifInfos.get(localExifId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (exifInfo != null) {
 | 
					      if (exifInfo != null) {
 | 
				
			||||||
        exifInfo.description = result!.exifInfo!.description;
 | 
					        exifInfo.description = description;
 | 
				
			||||||
        await _db.writeTxn(
 | 
					        await _db.writeTxn(
 | 
				
			||||||
          () => _db.exifInfos.put(exifInfo),
 | 
					          () => _db.exifInfos.put(exifInfo),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<String> readLatest(String assetRemoteId, int localExifId) async {
 | 
					 | 
				
			||||||
    final latestAssetFromServer =
 | 
					 | 
				
			||||||
        await _api.assetsApi.getAssetInfo(assetRemoteId);
 | 
					 | 
				
			||||||
    final localExifInfo = await _db.exifInfos.get(localExifId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (latestAssetFromServer != null && localExifInfo != null) {
 | 
					 | 
				
			||||||
      localExifInfo.description =
 | 
					 | 
				
			||||||
          latestAssetFromServer.exifInfo?.description ?? '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await _db.writeTxn(
 | 
					 | 
				
			||||||
        () => _db.exifInfos.put(localExifInfo),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return localExifInfo.description!;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return "";
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final assetDescriptionServiceProvider = Provider(
 | 
					final assetDescriptionServiceProvider = Provider(
 | 
				
			||||||
 | 
				
			|||||||
@ -2,10 +2,11 @@ import 'package:easy_localization/easy_localization.dart';
 | 
				
			|||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/entities/exif_info.entity.dart';
 | 
				
			||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | 
					import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/asset_viewer/asset_description.provider.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/entities/asset.entity.dart';
 | 
					import 'package:immich_mobile/entities/asset.entity.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/user.provider.dart';
 | 
					import 'package:immich_mobile/providers/user.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/services/asset_description.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
 | 
					import 'package:immich_mobile/widgets/common/immich_toast.dart';
 | 
				
			||||||
import 'package:logging/logging.dart';
 | 
					import 'package:logging/logging.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -13,9 +14,11 @@ class DescriptionInput extends HookConsumerWidget {
 | 
				
			|||||||
  DescriptionInput({
 | 
					  DescriptionInput({
 | 
				
			||||||
    super.key,
 | 
					    super.key,
 | 
				
			||||||
    required this.asset,
 | 
					    required this.asset,
 | 
				
			||||||
 | 
					    this.exifInfo,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final Asset asset;
 | 
					  final Asset asset;
 | 
				
			||||||
 | 
					  final ExifInfo? exifInfo;
 | 
				
			||||||
  final Logger _log = Logger('DescriptionInput');
 | 
					  final Logger _log = Logger('DescriptionInput');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@ -25,25 +28,25 @@ class DescriptionInput extends HookConsumerWidget {
 | 
				
			|||||||
    final focusNode = useFocusNode();
 | 
					    final focusNode = useFocusNode();
 | 
				
			||||||
    final isFocus = useState(false);
 | 
					    final isFocus = useState(false);
 | 
				
			||||||
    final isTextEmpty = useState(controller.text.isEmpty);
 | 
					    final isTextEmpty = useState(controller.text.isEmpty);
 | 
				
			||||||
    final descriptionProvider =
 | 
					    final descriptionProvider = ref.watch(assetDescriptionServiceProvider);
 | 
				
			||||||
        ref.watch(assetDescriptionProvider(asset).notifier);
 | 
					
 | 
				
			||||||
    final description = ref.watch(assetDescriptionProvider(asset));
 | 
					 | 
				
			||||||
    final owner = ref.watch(currentUserProvider);
 | 
					    final owner = ref.watch(currentUserProvider);
 | 
				
			||||||
    final hasError = useState(false);
 | 
					    final hasError = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(
 | 
					    useEffect(
 | 
				
			||||||
      () {
 | 
					      () {
 | 
				
			||||||
        controller.text = description;
 | 
					        controller.text = exifInfo?.description ?? '';
 | 
				
			||||||
        isTextEmpty.value = description.isEmpty;
 | 
					        isTextEmpty.value = exifInfo?.description?.isEmpty ?? true;
 | 
				
			||||||
        return null;
 | 
					        return null;
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      [description],
 | 
					      [exifInfo?.description],
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    submitDescription(String description) async {
 | 
					    submitDescription(String description) async {
 | 
				
			||||||
      hasError.value = false;
 | 
					      hasError.value = false;
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        await descriptionProvider.setDescription(
 | 
					        await descriptionProvider.setDescription(
 | 
				
			||||||
 | 
					          asset,
 | 
				
			||||||
          description,
 | 
					          description,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      } catch (error, stack) {
 | 
					      } catch (error, stack) {
 | 
				
			||||||
@ -85,7 +88,7 @@ class DescriptionInput extends HookConsumerWidget {
 | 
				
			|||||||
        isFocus.value = false;
 | 
					        isFocus.value = false;
 | 
				
			||||||
        focusNode.unfocus();
 | 
					        focusNode.unfocus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (description != controller.text) {
 | 
					        if (exifInfo?.description != controller.text) {
 | 
				
			||||||
          await submitDescription(controller.text);
 | 
					          await submitDescription(controller.text);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
				
			|||||||
@ -73,7 +73,8 @@ class ExifBottomSheet extends HookConsumerWidget {
 | 
				
			|||||||
                  child: Column(
 | 
					                  child: Column(
 | 
				
			||||||
                    children: [
 | 
					                    children: [
 | 
				
			||||||
                      dateWidget,
 | 
					                      dateWidget,
 | 
				
			||||||
                      if (asset.isRemote) DescriptionInput(asset: asset),
 | 
					                      if (asset.isRemote)
 | 
				
			||||||
 | 
					                        DescriptionInput(asset: asset, exifInfo: exifInfo),
 | 
				
			||||||
                    ],
 | 
					                    ],
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
@ -132,7 +133,8 @@ class ExifBottomSheet extends HookConsumerWidget {
 | 
				
			|||||||
                child: Column(
 | 
					                child: Column(
 | 
				
			||||||
                  children: [
 | 
					                  children: [
 | 
				
			||||||
                    dateWidget,
 | 
					                    dateWidget,
 | 
				
			||||||
                    if (asset.isRemote) DescriptionInput(asset: asset),
 | 
					                    if (asset.isRemote)
 | 
				
			||||||
 | 
					                      DescriptionInput(asset: asset, exifInfo: exifInfo),
 | 
				
			||||||
                    Padding(
 | 
					                    Padding(
 | 
				
			||||||
                      padding: EdgeInsets.only(top: asset.isRemote ? 0 : 16.0),
 | 
					                      padding: EdgeInsets.only(top: asset.isRemote ? 0 : 16.0),
 | 
				
			||||||
                      child: ExifLocation(
 | 
					                      child: ExifLocation(
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user