mirror of
https://github.com/immich-app/immich.git
synced 2025-03-01 15:11:21 +01: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
4 changed files with 32 additions and 123 deletions
|
@ -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…
Add table
Reference in a new issue