Merge branch 'main' into cool-app-bar
This commit is contained in:
@@ -2,16 +2,20 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
class AssetService {
|
||||
final RemoteAssetRepository _remoteAssetRepository;
|
||||
final DriftLocalAssetRepository _localAssetRepository;
|
||||
final Platform _platform;
|
||||
|
||||
const AssetService({
|
||||
required RemoteAssetRepository remoteAssetRepository,
|
||||
required DriftLocalAssetRepository localAssetRepository,
|
||||
}) : _remoteAssetRepository = remoteAssetRepository,
|
||||
_localAssetRepository = localAssetRepository;
|
||||
_localAssetRepository = localAssetRepository,
|
||||
_platform = const LocalPlatform();
|
||||
|
||||
Stream<BaseAsset?> watchAsset(BaseAsset asset) {
|
||||
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id;
|
||||
@@ -21,10 +25,40 @@ class AssetService {
|
||||
}
|
||||
|
||||
Future<ExifInfo?> getExif(BaseAsset asset) async {
|
||||
if (asset is LocalAsset || asset is! RemoteAsset) {
|
||||
if (!asset.hasRemote) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return _remoteAssetRepository.getExif(asset.id);
|
||||
final id =
|
||||
asset is LocalAsset ? asset.remoteId! : (asset as RemoteAsset).id;
|
||||
return _remoteAssetRepository.getExif(id);
|
||||
}
|
||||
|
||||
Future<double> getAspectRatio(BaseAsset asset) async {
|
||||
bool isFlipped;
|
||||
double? width;
|
||||
double? height;
|
||||
|
||||
if (asset.hasRemote) {
|
||||
final exif = await getExif(asset);
|
||||
isFlipped = ExifDtoConverter.isOrientationFlipped(exif?.orientation);
|
||||
width = exif?.width ?? asset.width?.toDouble();
|
||||
height = exif?.height ?? asset.height?.toDouble();
|
||||
} else if (asset is LocalAsset) {
|
||||
isFlipped = _platform.isAndroid &&
|
||||
(asset.orientation == 90 || asset.orientation == 270);
|
||||
width = asset.width?.toDouble();
|
||||
height = asset.height?.toDouble();
|
||||
} else {
|
||||
isFlipped = false;
|
||||
}
|
||||
|
||||
final orientedWidth = isFlipped ? height : width;
|
||||
final orientedHeight = isFlipped ? width : height;
|
||||
if (orientedWidth != null && orientedHeight != null && orientedHeight > 0) {
|
||||
return orientedWidth / orientedHeight;
|
||||
}
|
||||
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class HashService {
|
||||
final toHash = <_AssetToPath>[];
|
||||
|
||||
for (final asset in assetsToHash) {
|
||||
final file = await _storageRepository.getFileForAsset(asset);
|
||||
final file = await _storageRepository.getFileForAsset(asset.id);
|
||||
if (file == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -359,6 +359,7 @@ extension on Iterable<PlatformAsset> {
|
||||
width: e.width,
|
||||
height: e.height,
|
||||
durationInSeconds: e.durationInSeconds,
|
||||
orientation: e.orientation,
|
||||
),
|
||||
).toList();
|
||||
}
|
||||
|
||||
@@ -108,6 +108,11 @@ class TimelineFactory {
|
||||
class TimelineService {
|
||||
final TimelineAssetSource _assetSource;
|
||||
final TimelineBucketSource _bucketSource;
|
||||
final AsyncMutex _mutex = AsyncMutex();
|
||||
int _bufferOffset = 0;
|
||||
List<BaseAsset> _buffer = [];
|
||||
StreamSubscription? _bucketSubscription;
|
||||
|
||||
int _totalAssets = 0;
|
||||
int get totalAssets => _totalAssets;
|
||||
|
||||
@@ -117,24 +122,41 @@ class TimelineService {
|
||||
}) : _assetSource = assetSource,
|
||||
_bucketSource = bucketSource {
|
||||
_bucketSubscription = _bucketSource().listen((buckets) {
|
||||
_totalAssets =
|
||||
buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
|
||||
unawaited(_reloadBucket());
|
||||
_mutex.run(() async {
|
||||
final totalAssets =
|
||||
buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
|
||||
|
||||
if (totalAssets == 0) {
|
||||
_bufferOffset = 0;
|
||||
_buffer.clear();
|
||||
} else {
|
||||
final int offset;
|
||||
final int count;
|
||||
// When the buffer is empty or the old bufferOffset is greater than the new total assets,
|
||||
// we need to reset the buffer and load the first batch of assets.
|
||||
if (_bufferOffset >= totalAssets || _buffer.isEmpty) {
|
||||
offset = 0;
|
||||
count = kTimelineAssetLoadBatchSize;
|
||||
} else {
|
||||
offset = _bufferOffset;
|
||||
count = math.min(
|
||||
_buffer.length,
|
||||
totalAssets - _bufferOffset,
|
||||
);
|
||||
}
|
||||
_buffer = await _assetSource(offset, count);
|
||||
_bufferOffset = offset;
|
||||
}
|
||||
|
||||
// change the state's total assets count only after the buffer is reloaded
|
||||
_totalAssets = totalAssets;
|
||||
EventStream.shared.emit(const TimelineReloadEvent());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
final AsyncMutex _mutex = AsyncMutex();
|
||||
int _bufferOffset = 0;
|
||||
List<BaseAsset> _buffer = [];
|
||||
StreamSubscription? _bucketSubscription;
|
||||
|
||||
Stream<List<Bucket>> Function() get watchBuckets => _bucketSource;
|
||||
|
||||
Future<void> _reloadBucket() => _mutex.run(() async {
|
||||
_buffer = await _assetSource(_bufferOffset, _buffer.length);
|
||||
EventStream.shared.emit(const TimelineReloadEvent());
|
||||
});
|
||||
|
||||
Future<List<BaseAsset>> loadAssets(int index, int count) =>
|
||||
_mutex.run(() => _loadAssets(index, count));
|
||||
|
||||
@@ -163,18 +185,20 @@ class TimelineService {
|
||||
: (len > kTimelineAssetLoadBatchSize ? index : index + count - len),
|
||||
);
|
||||
|
||||
final assets = await _assetSource(start, len);
|
||||
_buffer = assets;
|
||||
_buffer = await _assetSource(start, len);
|
||||
_bufferOffset = start;
|
||||
|
||||
return getAssets(index, count);
|
||||
}
|
||||
|
||||
bool hasRange(int index, int count) =>
|
||||
index >= _bufferOffset && index + count <= _bufferOffset + _buffer.length;
|
||||
index >= 0 &&
|
||||
index < _totalAssets &&
|
||||
index >= _bufferOffset &&
|
||||
index + count <= _bufferOffset + _buffer.length &&
|
||||
index + count <= _totalAssets;
|
||||
|
||||
List<BaseAsset> getAssets(int index, int count) {
|
||||
assert(index + count <= totalAssets);
|
||||
if (!hasRange(index, count)) {
|
||||
throw RangeError('TimelineService::getAssets Index out of range');
|
||||
}
|
||||
@@ -184,14 +208,16 @@ class TimelineService {
|
||||
|
||||
// Pre-cache assets around the given index for asset viewer
|
||||
Future<void> preCacheAssets(int index) =>
|
||||
_mutex.run(() => _loadAssets(index, 5));
|
||||
_mutex.run(() => _loadAssets(index, math.min(5, _totalAssets - index)));
|
||||
|
||||
BaseAsset getRandomAsset() =>
|
||||
_buffer.elementAt(math.Random().nextInt(_buffer.length));
|
||||
|
||||
BaseAsset getAsset(int index) {
|
||||
if (!hasRange(index, 1)) {
|
||||
throw RangeError('TimelineService::getAsset Index out of range');
|
||||
throw RangeError(
|
||||
'TimelineService::getAsset Index $index not in buffer range [$_bufferOffset, ${_bufferOffset + _buffer.length})',
|
||||
);
|
||||
}
|
||||
return _buffer.elementAt(index - _bufferOffset);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user