mirror of
https://github.com/immich-app/immich.git
synced 2026-07-25 05:50:40 +03:00
Compare commits
4 Commits
chore/free
...
refactor/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74f9a5cbad | ||
|
|
5fa920a5f0 | ||
|
|
acc7e6b299 | ||
|
|
3af94a4805 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -15,9 +15,6 @@ mobile/ios/**/*.g.swift linguist-generated=true
|
||||
mobile/lib/**/*.drift.dart -diff -merge
|
||||
mobile/lib/**/*.drift.dart linguist-generated=true
|
||||
|
||||
mobile/lib/**/*.freezed.dart -diff -merge
|
||||
mobile/lib/**/*.freezed.dart linguist-generated=true
|
||||
|
||||
mobile/drift_schemas/main/drift_schema_*.json -diff -merge
|
||||
mobile/drift_schemas/main/drift_schema_*.json linguist-generated=true
|
||||
|
||||
|
||||
@@ -18,15 +18,21 @@ struct ImmichWidgetView: View {
|
||||
|
||||
var body: some View {
|
||||
if entry.image == nil {
|
||||
VStack {
|
||||
Image("LaunchImage")
|
||||
.tintedWidgetImageModifier()
|
||||
Text(entry.metadata.error?.errorDescription ?? "")
|
||||
.minimumScaleFactor(0.25)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(16)
|
||||
Image("LaunchImage")
|
||||
.tintedWidgetImageModifier()
|
||||
.overlay(alignment: .bottom) {
|
||||
if let error = entry.metadata.error?.errorDescription {
|
||||
Text(error)
|
||||
.minimumScaleFactor(0.25)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
.alignmentGuide(.bottom) { dimensions in
|
||||
// Place the text below the bottom of the image
|
||||
dimensions[.top] - 8
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ZStack(alignment: .leading) {
|
||||
Color.clear.overlay(
|
||||
|
||||
@@ -60,6 +60,7 @@ sealed class BaseAsset {
|
||||
bool get hasLocal => storage == AssetState.local || storage == AssetState.merged;
|
||||
bool get isLocalOnly => storage == AssetState.local;
|
||||
bool get isRemoteOnly => storage == AssetState.remote;
|
||||
bool get isMerged => storage == .merged;
|
||||
|
||||
// Same asset even if localId is known on one side but not the other (heroTag isn't stable then)
|
||||
bool refersToSameAsset(BaseAsset other) {
|
||||
|
||||
@@ -74,6 +74,8 @@ class RemoteAsset extends BaseAsset {
|
||||
|
||||
bool get isArchived => visibility == .archive;
|
||||
|
||||
bool get isLocked => visibility == .locked;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''Asset {
|
||||
|
||||
@@ -1,39 +1,30 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
class ExifInfo {
|
||||
final int? assetId;
|
||||
final int? fileSize;
|
||||
final String? description;
|
||||
final bool isFlipped;
|
||||
final String? orientation;
|
||||
final String? timeZone;
|
||||
final DateTime? dateTimeOriginal;
|
||||
final int? rating;
|
||||
final int? width;
|
||||
final int? height;
|
||||
|
||||
part 'exif.model.freezed.dart';
|
||||
// GPS
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
final String? city;
|
||||
final String? state;
|
||||
final String? country;
|
||||
|
||||
@freezed
|
||||
abstract class ExifInfo with _$ExifInfo {
|
||||
const ExifInfo._();
|
||||
|
||||
const factory ExifInfo({
|
||||
int? assetId,
|
||||
int? fileSize,
|
||||
String? description,
|
||||
@Default(false) bool isFlipped,
|
||||
String? orientation,
|
||||
String? timeZone,
|
||||
DateTime? dateTimeOriginal,
|
||||
int? rating,
|
||||
int? width,
|
||||
int? height,
|
||||
|
||||
// GPS
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
String? city,
|
||||
String? state,
|
||||
String? country,
|
||||
|
||||
// Camera related
|
||||
String? make,
|
||||
String? model,
|
||||
String? lens,
|
||||
double? f,
|
||||
double? mm,
|
||||
int? iso,
|
||||
double? exposureSeconds,
|
||||
}) = _ExifInfo;
|
||||
// Camera related
|
||||
final String? make;
|
||||
final String? model;
|
||||
final String? lens;
|
||||
final double? f;
|
||||
final double? mm;
|
||||
final int? iso;
|
||||
final double? exposureSeconds;
|
||||
|
||||
bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0;
|
||||
|
||||
@@ -50,4 +41,162 @@ abstract class ExifInfo with _$ExifInfo {
|
||||
String get fNumber => f == null ? "" : f!.toStringAsFixed(1);
|
||||
|
||||
String get focalLength => mm == null ? "" : mm!.toStringAsFixed(3);
|
||||
|
||||
const ExifInfo({
|
||||
this.assetId,
|
||||
this.fileSize,
|
||||
this.description,
|
||||
this.orientation,
|
||||
this.timeZone,
|
||||
this.dateTimeOriginal,
|
||||
this.rating,
|
||||
this.width,
|
||||
this.height,
|
||||
this.isFlipped = false,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
this.city,
|
||||
this.state,
|
||||
this.country,
|
||||
this.make,
|
||||
this.model,
|
||||
this.lens,
|
||||
this.f,
|
||||
this.mm,
|
||||
this.iso,
|
||||
this.exposureSeconds,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(covariant ExifInfo other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other.fileSize == fileSize &&
|
||||
other.description == description &&
|
||||
other.isFlipped == isFlipped &&
|
||||
other.orientation == orientation &&
|
||||
other.timeZone == timeZone &&
|
||||
other.dateTimeOriginal == dateTimeOriginal &&
|
||||
other.rating == rating &&
|
||||
other.width == width &&
|
||||
other.height == height &&
|
||||
other.latitude == latitude &&
|
||||
other.longitude == longitude &&
|
||||
other.city == city &&
|
||||
other.state == state &&
|
||||
other.country == country &&
|
||||
other.make == make &&
|
||||
other.model == model &&
|
||||
other.lens == lens &&
|
||||
other.f == f &&
|
||||
other.mm == mm &&
|
||||
other.iso == iso &&
|
||||
other.exposureSeconds == exposureSeconds &&
|
||||
other.assetId == assetId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return fileSize.hashCode ^
|
||||
description.hashCode ^
|
||||
orientation.hashCode ^
|
||||
isFlipped.hashCode ^
|
||||
timeZone.hashCode ^
|
||||
dateTimeOriginal.hashCode ^
|
||||
rating.hashCode ^
|
||||
width.hashCode ^
|
||||
height.hashCode ^
|
||||
latitude.hashCode ^
|
||||
longitude.hashCode ^
|
||||
city.hashCode ^
|
||||
state.hashCode ^
|
||||
country.hashCode ^
|
||||
make.hashCode ^
|
||||
model.hashCode ^
|
||||
lens.hashCode ^
|
||||
f.hashCode ^
|
||||
mm.hashCode ^
|
||||
iso.hashCode ^
|
||||
exposureSeconds.hashCode ^
|
||||
assetId.hashCode;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''{
|
||||
fileSize: ${fileSize ?? 'NA'},
|
||||
description: ${description ?? 'NA'},
|
||||
orientation: ${orientation ?? 'NA'},
|
||||
isFlipped: $isFlipped,
|
||||
timeZone: ${timeZone ?? 'NA'},
|
||||
dateTimeOriginal: ${dateTimeOriginal ?? 'NA'},
|
||||
rating: ${rating ?? 'NA'},
|
||||
width: ${width ?? 'NA'},
|
||||
height: ${height ?? 'NA'},
|
||||
latitude: ${latitude ?? 'NA'},
|
||||
longitude: ${longitude ?? 'NA'},
|
||||
city: ${city ?? 'NA'},
|
||||
state: ${state ?? 'NA'},
|
||||
country: ${country ?? '<NA>'},
|
||||
make: ${make ?? 'NA'},
|
||||
model: ${model ?? 'NA'},
|
||||
lens: ${lens ?? 'NA'},
|
||||
f: ${f ?? 'NA'},
|
||||
mm: ${mm ?? '<NA>'},
|
||||
iso: ${iso ?? 'NA'},
|
||||
exposureSeconds: ${exposureSeconds ?? 'NA'},
|
||||
}''';
|
||||
}
|
||||
|
||||
ExifInfo copyWith({
|
||||
int? assetId,
|
||||
int? fileSize,
|
||||
String? description,
|
||||
String? orientation,
|
||||
String? timeZone,
|
||||
DateTime? dateTimeOriginal,
|
||||
int? rating,
|
||||
int? width,
|
||||
int? height,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
String? city,
|
||||
String? state,
|
||||
String? country,
|
||||
bool? isFlipped,
|
||||
String? make,
|
||||
String? model,
|
||||
String? lens,
|
||||
double? f,
|
||||
double? mm,
|
||||
int? iso,
|
||||
double? exposureSeconds,
|
||||
}) {
|
||||
return ExifInfo(
|
||||
assetId: assetId ?? this.assetId,
|
||||
fileSize: fileSize ?? this.fileSize,
|
||||
description: description ?? this.description,
|
||||
orientation: orientation ?? this.orientation,
|
||||
timeZone: timeZone ?? this.timeZone,
|
||||
dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal,
|
||||
rating: rating ?? this.rating,
|
||||
width: width ?? this.width,
|
||||
height: height ?? this.height,
|
||||
isFlipped: isFlipped ?? this.isFlipped,
|
||||
latitude: latitude ?? this.latitude,
|
||||
longitude: longitude ?? this.longitude,
|
||||
city: city ?? this.city,
|
||||
state: state ?? this.state,
|
||||
country: country ?? this.country,
|
||||
make: make ?? this.make,
|
||||
model: model ?? this.model,
|
||||
lens: lens ?? this.lens,
|
||||
f: f ?? this.f,
|
||||
mm: mm ?? this.mm,
|
||||
iso: iso ?? this.iso,
|
||||
exposureSeconds: exposureSeconds ?? this.exposureSeconds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,338 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'exif.model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$ExifInfo {
|
||||
|
||||
int? get assetId; int? get fileSize; String? get description; bool get isFlipped; String? get orientation; String? get timeZone; DateTime? get dateTimeOriginal; int? get rating; int? get width; int? get height;// GPS
|
||||
double? get latitude; double? get longitude; String? get city; String? get state; String? get country;// Camera related
|
||||
String? get make; String? get model; String? get lens; double? get f; double? get mm; int? get iso; double? get exposureSeconds;
|
||||
/// Create a copy of ExifInfo
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$ExifInfoCopyWith<ExifInfo> get copyWith => _$ExifInfoCopyWithImpl<ExifInfo>(this as ExifInfo, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ExifInfo&&(identical(other.assetId, assetId) || other.assetId == assetId)&&(identical(other.fileSize, fileSize) || other.fileSize == fileSize)&&(identical(other.description, description) || other.description == description)&&(identical(other.isFlipped, isFlipped) || other.isFlipped == isFlipped)&&(identical(other.orientation, orientation) || other.orientation == orientation)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.dateTimeOriginal, dateTimeOriginal) || other.dateTimeOriginal == dateTimeOriginal)&&(identical(other.rating, rating) || other.rating == rating)&&(identical(other.width, width) || other.width == width)&&(identical(other.height, height) || other.height == height)&&(identical(other.latitude, latitude) || other.latitude == latitude)&&(identical(other.longitude, longitude) || other.longitude == longitude)&&(identical(other.city, city) || other.city == city)&&(identical(other.state, state) || other.state == state)&&(identical(other.country, country) || other.country == country)&&(identical(other.make, make) || other.make == make)&&(identical(other.model, model) || other.model == model)&&(identical(other.lens, lens) || other.lens == lens)&&(identical(other.f, f) || other.f == f)&&(identical(other.mm, mm) || other.mm == mm)&&(identical(other.iso, iso) || other.iso == iso)&&(identical(other.exposureSeconds, exposureSeconds) || other.exposureSeconds == exposureSeconds));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll([runtimeType,assetId,fileSize,description,isFlipped,orientation,timeZone,dateTimeOriginal,rating,width,height,latitude,longitude,city,state,country,make,model,lens,f,mm,iso,exposureSeconds]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ExifInfo(assetId: $assetId, fileSize: $fileSize, description: $description, isFlipped: $isFlipped, orientation: $orientation, timeZone: $timeZone, dateTimeOriginal: $dateTimeOriginal, rating: $rating, width: $width, height: $height, latitude: $latitude, longitude: $longitude, city: $city, state: $state, country: $country, make: $make, model: $model, lens: $lens, f: $f, mm: $mm, iso: $iso, exposureSeconds: $exposureSeconds)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $ExifInfoCopyWith<$Res> {
|
||||
factory $ExifInfoCopyWith(ExifInfo value, $Res Function(ExifInfo) _then) = _$ExifInfoCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
int? assetId, int? fileSize, String? description, bool isFlipped, String? orientation, String? timeZone, DateTime? dateTimeOriginal, int? rating, int? width, int? height, double? latitude, double? longitude, String? city, String? state, String? country, String? make, String? model, String? lens, double? f, double? mm, int? iso, double? exposureSeconds
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$ExifInfoCopyWithImpl<$Res>
|
||||
implements $ExifInfoCopyWith<$Res> {
|
||||
_$ExifInfoCopyWithImpl(this._self, this._then);
|
||||
|
||||
final ExifInfo _self;
|
||||
final $Res Function(ExifInfo) _then;
|
||||
|
||||
/// Create a copy of ExifInfo
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? assetId = freezed,Object? fileSize = freezed,Object? description = freezed,Object? isFlipped = null,Object? orientation = freezed,Object? timeZone = freezed,Object? dateTimeOriginal = freezed,Object? rating = freezed,Object? width = freezed,Object? height = freezed,Object? latitude = freezed,Object? longitude = freezed,Object? city = freezed,Object? state = freezed,Object? country = freezed,Object? make = freezed,Object? model = freezed,Object? lens = freezed,Object? f = freezed,Object? mm = freezed,Object? iso = freezed,Object? exposureSeconds = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
assetId: freezed == assetId ? _self.assetId : assetId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,fileSize: freezed == fileSize ? _self.fileSize : fileSize // ignore: cast_nullable_to_non_nullable
|
||||
as int?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,isFlipped: null == isFlipped ? _self.isFlipped : isFlipped // ignore: cast_nullable_to_non_nullable
|
||||
as bool,orientation: freezed == orientation ? _self.orientation : orientation // ignore: cast_nullable_to_non_nullable
|
||||
as String?,timeZone: freezed == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable
|
||||
as String?,dateTimeOriginal: freezed == dateTimeOriginal ? _self.dateTimeOriginal : dateTimeOriginal // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,rating: freezed == rating ? _self.rating : rating // ignore: cast_nullable_to_non_nullable
|
||||
as int?,width: freezed == width ? _self.width : width // ignore: cast_nullable_to_non_nullable
|
||||
as int?,height: freezed == height ? _self.height : height // ignore: cast_nullable_to_non_nullable
|
||||
as int?,latitude: freezed == latitude ? _self.latitude : latitude // ignore: cast_nullable_to_non_nullable
|
||||
as double?,longitude: freezed == longitude ? _self.longitude : longitude // ignore: cast_nullable_to_non_nullable
|
||||
as double?,city: freezed == city ? _self.city : city // ignore: cast_nullable_to_non_nullable
|
||||
as String?,state: freezed == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
|
||||
as String?,country: freezed == country ? _self.country : country // ignore: cast_nullable_to_non_nullable
|
||||
as String?,make: freezed == make ? _self.make : make // ignore: cast_nullable_to_non_nullable
|
||||
as String?,model: freezed == model ? _self.model : model // ignore: cast_nullable_to_non_nullable
|
||||
as String?,lens: freezed == lens ? _self.lens : lens // ignore: cast_nullable_to_non_nullable
|
||||
as String?,f: freezed == f ? _self.f : f // ignore: cast_nullable_to_non_nullable
|
||||
as double?,mm: freezed == mm ? _self.mm : mm // ignore: cast_nullable_to_non_nullable
|
||||
as double?,iso: freezed == iso ? _self.iso : iso // ignore: cast_nullable_to_non_nullable
|
||||
as int?,exposureSeconds: freezed == exposureSeconds ? _self.exposureSeconds : exposureSeconds // ignore: cast_nullable_to_non_nullable
|
||||
as double?,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [ExifInfo].
|
||||
extension ExifInfoPatterns on ExifInfo {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ExifInfo value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ExifInfo() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ExifInfo value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ExifInfo():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ExifInfo value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ExifInfo() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int? assetId, int? fileSize, String? description, bool isFlipped, String? orientation, String? timeZone, DateTime? dateTimeOriginal, int? rating, int? width, int? height, double? latitude, double? longitude, String? city, String? state, String? country, String? make, String? model, String? lens, double? f, double? mm, int? iso, double? exposureSeconds)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ExifInfo() when $default != null:
|
||||
return $default(_that.assetId,_that.fileSize,_that.description,_that.isFlipped,_that.orientation,_that.timeZone,_that.dateTimeOriginal,_that.rating,_that.width,_that.height,_that.latitude,_that.longitude,_that.city,_that.state,_that.country,_that.make,_that.model,_that.lens,_that.f,_that.mm,_that.iso,_that.exposureSeconds);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int? assetId, int? fileSize, String? description, bool isFlipped, String? orientation, String? timeZone, DateTime? dateTimeOriginal, int? rating, int? width, int? height, double? latitude, double? longitude, String? city, String? state, String? country, String? make, String? model, String? lens, double? f, double? mm, int? iso, double? exposureSeconds) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ExifInfo():
|
||||
return $default(_that.assetId,_that.fileSize,_that.description,_that.isFlipped,_that.orientation,_that.timeZone,_that.dateTimeOriginal,_that.rating,_that.width,_that.height,_that.latitude,_that.longitude,_that.city,_that.state,_that.country,_that.make,_that.model,_that.lens,_that.f,_that.mm,_that.iso,_that.exposureSeconds);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int? assetId, int? fileSize, String? description, bool isFlipped, String? orientation, String? timeZone, DateTime? dateTimeOriginal, int? rating, int? width, int? height, double? latitude, double? longitude, String? city, String? state, String? country, String? make, String? model, String? lens, double? f, double? mm, int? iso, double? exposureSeconds)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ExifInfo() when $default != null:
|
||||
return $default(_that.assetId,_that.fileSize,_that.description,_that.isFlipped,_that.orientation,_that.timeZone,_that.dateTimeOriginal,_that.rating,_that.width,_that.height,_that.latitude,_that.longitude,_that.city,_that.state,_that.country,_that.make,_that.model,_that.lens,_that.f,_that.mm,_that.iso,_that.exposureSeconds);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _ExifInfo extends ExifInfo {
|
||||
const _ExifInfo({this.assetId, this.fileSize, this.description, this.isFlipped = false, this.orientation, this.timeZone, this.dateTimeOriginal, this.rating, this.width, this.height, this.latitude, this.longitude, this.city, this.state, this.country, this.make, this.model, this.lens, this.f, this.mm, this.iso, this.exposureSeconds}): super._();
|
||||
|
||||
|
||||
@override final int? assetId;
|
||||
@override final int? fileSize;
|
||||
@override final String? description;
|
||||
@override@JsonKey() final bool isFlipped;
|
||||
@override final String? orientation;
|
||||
@override final String? timeZone;
|
||||
@override final DateTime? dateTimeOriginal;
|
||||
@override final int? rating;
|
||||
@override final int? width;
|
||||
@override final int? height;
|
||||
// GPS
|
||||
@override final double? latitude;
|
||||
@override final double? longitude;
|
||||
@override final String? city;
|
||||
@override final String? state;
|
||||
@override final String? country;
|
||||
// Camera related
|
||||
@override final String? make;
|
||||
@override final String? model;
|
||||
@override final String? lens;
|
||||
@override final double? f;
|
||||
@override final double? mm;
|
||||
@override final int? iso;
|
||||
@override final double? exposureSeconds;
|
||||
|
||||
/// Create a copy of ExifInfo
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$ExifInfoCopyWith<_ExifInfo> get copyWith => __$ExifInfoCopyWithImpl<_ExifInfo>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ExifInfo&&(identical(other.assetId, assetId) || other.assetId == assetId)&&(identical(other.fileSize, fileSize) || other.fileSize == fileSize)&&(identical(other.description, description) || other.description == description)&&(identical(other.isFlipped, isFlipped) || other.isFlipped == isFlipped)&&(identical(other.orientation, orientation) || other.orientation == orientation)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.dateTimeOriginal, dateTimeOriginal) || other.dateTimeOriginal == dateTimeOriginal)&&(identical(other.rating, rating) || other.rating == rating)&&(identical(other.width, width) || other.width == width)&&(identical(other.height, height) || other.height == height)&&(identical(other.latitude, latitude) || other.latitude == latitude)&&(identical(other.longitude, longitude) || other.longitude == longitude)&&(identical(other.city, city) || other.city == city)&&(identical(other.state, state) || other.state == state)&&(identical(other.country, country) || other.country == country)&&(identical(other.make, make) || other.make == make)&&(identical(other.model, model) || other.model == model)&&(identical(other.lens, lens) || other.lens == lens)&&(identical(other.f, f) || other.f == f)&&(identical(other.mm, mm) || other.mm == mm)&&(identical(other.iso, iso) || other.iso == iso)&&(identical(other.exposureSeconds, exposureSeconds) || other.exposureSeconds == exposureSeconds));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll([runtimeType,assetId,fileSize,description,isFlipped,orientation,timeZone,dateTimeOriginal,rating,width,height,latitude,longitude,city,state,country,make,model,lens,f,mm,iso,exposureSeconds]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ExifInfo(assetId: $assetId, fileSize: $fileSize, description: $description, isFlipped: $isFlipped, orientation: $orientation, timeZone: $timeZone, dateTimeOriginal: $dateTimeOriginal, rating: $rating, width: $width, height: $height, latitude: $latitude, longitude: $longitude, city: $city, state: $state, country: $country, make: $make, model: $model, lens: $lens, f: $f, mm: $mm, iso: $iso, exposureSeconds: $exposureSeconds)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$ExifInfoCopyWith<$Res> implements $ExifInfoCopyWith<$Res> {
|
||||
factory _$ExifInfoCopyWith(_ExifInfo value, $Res Function(_ExifInfo) _then) = __$ExifInfoCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
int? assetId, int? fileSize, String? description, bool isFlipped, String? orientation, String? timeZone, DateTime? dateTimeOriginal, int? rating, int? width, int? height, double? latitude, double? longitude, String? city, String? state, String? country, String? make, String? model, String? lens, double? f, double? mm, int? iso, double? exposureSeconds
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$ExifInfoCopyWithImpl<$Res>
|
||||
implements _$ExifInfoCopyWith<$Res> {
|
||||
__$ExifInfoCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _ExifInfo _self;
|
||||
final $Res Function(_ExifInfo) _then;
|
||||
|
||||
/// Create a copy of ExifInfo
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? assetId = freezed,Object? fileSize = freezed,Object? description = freezed,Object? isFlipped = null,Object? orientation = freezed,Object? timeZone = freezed,Object? dateTimeOriginal = freezed,Object? rating = freezed,Object? width = freezed,Object? height = freezed,Object? latitude = freezed,Object? longitude = freezed,Object? city = freezed,Object? state = freezed,Object? country = freezed,Object? make = freezed,Object? model = freezed,Object? lens = freezed,Object? f = freezed,Object? mm = freezed,Object? iso = freezed,Object? exposureSeconds = freezed,}) {
|
||||
return _then(_ExifInfo(
|
||||
assetId: freezed == assetId ? _self.assetId : assetId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,fileSize: freezed == fileSize ? _self.fileSize : fileSize // ignore: cast_nullable_to_non_nullable
|
||||
as int?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,isFlipped: null == isFlipped ? _self.isFlipped : isFlipped // ignore: cast_nullable_to_non_nullable
|
||||
as bool,orientation: freezed == orientation ? _self.orientation : orientation // ignore: cast_nullable_to_non_nullable
|
||||
as String?,timeZone: freezed == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable
|
||||
as String?,dateTimeOriginal: freezed == dateTimeOriginal ? _self.dateTimeOriginal : dateTimeOriginal // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,rating: freezed == rating ? _self.rating : rating // ignore: cast_nullable_to_non_nullable
|
||||
as int?,width: freezed == width ? _self.width : width // ignore: cast_nullable_to_non_nullable
|
||||
as int?,height: freezed == height ? _self.height : height // ignore: cast_nullable_to_non_nullable
|
||||
as int?,latitude: freezed == latitude ? _self.latitude : latitude // ignore: cast_nullable_to_non_nullable
|
||||
as double?,longitude: freezed == longitude ? _self.longitude : longitude // ignore: cast_nullable_to_non_nullable
|
||||
as double?,city: freezed == city ? _self.city : city // ignore: cast_nullable_to_non_nullable
|
||||
as String?,state: freezed == state ? _self.state : state // ignore: cast_nullable_to_non_nullable
|
||||
as String?,country: freezed == country ? _self.country : country // ignore: cast_nullable_to_non_nullable
|
||||
as String?,make: freezed == make ? _self.make : make // ignore: cast_nullable_to_non_nullable
|
||||
as String?,model: freezed == model ? _self.model : model // ignore: cast_nullable_to_non_nullable
|
||||
as String?,lens: freezed == lens ? _self.lens : lens // ignore: cast_nullable_to_non_nullable
|
||||
as String?,f: freezed == f ? _self.f : f // ignore: cast_nullable_to_non_nullable
|
||||
as double?,mm: freezed == mm ? _self.mm : mm // ignore: cast_nullable_to_non_nullable
|
||||
as double?,iso: freezed == iso ? _self.iso : iso // ignore: cast_nullable_to_non_nullable
|
||||
as int?,exposureSeconds: freezed == exposureSeconds ? _self.exposureSeconds : exposureSeconds // ignore: cast_nullable_to_non_nullable
|
||||
as double?,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,23 +1,128 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
class Ocr {
|
||||
final String id;
|
||||
final String assetId;
|
||||
final double x1;
|
||||
final double y1;
|
||||
final double x2;
|
||||
final double y2;
|
||||
final double x3;
|
||||
final double y3;
|
||||
final double x4;
|
||||
final double y4;
|
||||
final double boxScore;
|
||||
final double textScore;
|
||||
final String text;
|
||||
final bool isVisible;
|
||||
|
||||
part 'ocr.model.freezed.dart';
|
||||
const Ocr({
|
||||
required this.id,
|
||||
required this.assetId,
|
||||
required this.x1,
|
||||
required this.y1,
|
||||
required this.x2,
|
||||
required this.y2,
|
||||
required this.x3,
|
||||
required this.y3,
|
||||
required this.x4,
|
||||
required this.y4,
|
||||
required this.boxScore,
|
||||
required this.textScore,
|
||||
required this.text,
|
||||
required this.isVisible,
|
||||
});
|
||||
|
||||
@freezed
|
||||
abstract class Ocr with _$Ocr {
|
||||
const factory Ocr({
|
||||
required String id,
|
||||
required String assetId,
|
||||
required double x1,
|
||||
required double y1,
|
||||
required double x2,
|
||||
required double y2,
|
||||
required double x3,
|
||||
required double y3,
|
||||
required double x4,
|
||||
required double y4,
|
||||
required double boxScore,
|
||||
required double textScore,
|
||||
required String text,
|
||||
required bool isVisible,
|
||||
}) = _Ocr;
|
||||
Ocr copyWith({
|
||||
String? id,
|
||||
String? assetId,
|
||||
double? x1,
|
||||
double? y1,
|
||||
double? x2,
|
||||
double? y2,
|
||||
double? x3,
|
||||
double? y3,
|
||||
double? x4,
|
||||
double? y4,
|
||||
double? boxScore,
|
||||
double? textScore,
|
||||
String? text,
|
||||
bool? isVisible,
|
||||
}) {
|
||||
return Ocr(
|
||||
id: id ?? this.id,
|
||||
assetId: assetId ?? this.assetId,
|
||||
x1: x1 ?? this.x1,
|
||||
y1: y1 ?? this.y1,
|
||||
x2: x2 ?? this.x2,
|
||||
y2: y2 ?? this.y2,
|
||||
x3: x3 ?? this.x3,
|
||||
y3: y3 ?? this.y3,
|
||||
x4: x4 ?? this.x4,
|
||||
y4: y4 ?? this.y4,
|
||||
boxScore: boxScore ?? this.boxScore,
|
||||
textScore: textScore ?? this.textScore,
|
||||
text: text ?? this.text,
|
||||
isVisible: isVisible ?? this.isVisible,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''Ocr {
|
||||
id: $id,
|
||||
assetId: $assetId,
|
||||
x1: $x1,
|
||||
y1: $y1,
|
||||
x2: $x2,
|
||||
y2: $y2,
|
||||
x3: $x3,
|
||||
y3: $y3,
|
||||
x4: $x4,
|
||||
y4: $y4,
|
||||
boxScore: $boxScore,
|
||||
textScore: $textScore,
|
||||
text: $text,
|
||||
isVisible: $isVisible
|
||||
}''';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other is Ocr &&
|
||||
other.id == id &&
|
||||
other.assetId == assetId &&
|
||||
other.x1 == x1 &&
|
||||
other.y1 == y1 &&
|
||||
other.x2 == x2 &&
|
||||
other.y2 == y2 &&
|
||||
other.x3 == x3 &&
|
||||
other.y3 == y3 &&
|
||||
other.x4 == x4 &&
|
||||
other.y4 == y4 &&
|
||||
other.boxScore == boxScore &&
|
||||
other.textScore == textScore &&
|
||||
other.text == text &&
|
||||
other.isVisible == isVisible;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^
|
||||
assetId.hashCode ^
|
||||
x1.hashCode ^
|
||||
y1.hashCode ^
|
||||
x2.hashCode ^
|
||||
y2.hashCode ^
|
||||
x3.hashCode ^
|
||||
y3.hashCode ^
|
||||
x4.hashCode ^
|
||||
y4.hashCode ^
|
||||
boxScore.hashCode ^
|
||||
textScore.hashCode ^
|
||||
text.hashCode ^
|
||||
isVisible.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,310 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'ocr.model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$Ocr {
|
||||
|
||||
String get id; String get assetId; double get x1; double get y1; double get x2; double get y2; double get x3; double get y3; double get x4; double get y4; double get boxScore; double get textScore; String get text; bool get isVisible;
|
||||
/// Create a copy of Ocr
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$OcrCopyWith<Ocr> get copyWith => _$OcrCopyWithImpl<Ocr>(this as Ocr, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is Ocr&&(identical(other.id, id) || other.id == id)&&(identical(other.assetId, assetId) || other.assetId == assetId)&&(identical(other.x1, x1) || other.x1 == x1)&&(identical(other.y1, y1) || other.y1 == y1)&&(identical(other.x2, x2) || other.x2 == x2)&&(identical(other.y2, y2) || other.y2 == y2)&&(identical(other.x3, x3) || other.x3 == x3)&&(identical(other.y3, y3) || other.y3 == y3)&&(identical(other.x4, x4) || other.x4 == x4)&&(identical(other.y4, y4) || other.y4 == y4)&&(identical(other.boxScore, boxScore) || other.boxScore == boxScore)&&(identical(other.textScore, textScore) || other.textScore == textScore)&&(identical(other.text, text) || other.text == text)&&(identical(other.isVisible, isVisible) || other.isVisible == isVisible));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,assetId,x1,y1,x2,y2,x3,y3,x4,y4,boxScore,textScore,text,isVisible);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Ocr(id: $id, assetId: $assetId, x1: $x1, y1: $y1, x2: $x2, y2: $y2, x3: $x3, y3: $y3, x4: $x4, y4: $y4, boxScore: $boxScore, textScore: $textScore, text: $text, isVisible: $isVisible)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $OcrCopyWith<$Res> {
|
||||
factory $OcrCopyWith(Ocr value, $Res Function(Ocr) _then) = _$OcrCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String assetId, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double boxScore, double textScore, String text, bool isVisible
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$OcrCopyWithImpl<$Res>
|
||||
implements $OcrCopyWith<$Res> {
|
||||
_$OcrCopyWithImpl(this._self, this._then);
|
||||
|
||||
final Ocr _self;
|
||||
final $Res Function(Ocr) _then;
|
||||
|
||||
/// Create a copy of Ocr
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? assetId = null,Object? x1 = null,Object? y1 = null,Object? x2 = null,Object? y2 = null,Object? x3 = null,Object? y3 = null,Object? x4 = null,Object? y4 = null,Object? boxScore = null,Object? textScore = null,Object? text = null,Object? isVisible = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,assetId: null == assetId ? _self.assetId : assetId // ignore: cast_nullable_to_non_nullable
|
||||
as String,x1: null == x1 ? _self.x1 : x1 // ignore: cast_nullable_to_non_nullable
|
||||
as double,y1: null == y1 ? _self.y1 : y1 // ignore: cast_nullable_to_non_nullable
|
||||
as double,x2: null == x2 ? _self.x2 : x2 // ignore: cast_nullable_to_non_nullable
|
||||
as double,y2: null == y2 ? _self.y2 : y2 // ignore: cast_nullable_to_non_nullable
|
||||
as double,x3: null == x3 ? _self.x3 : x3 // ignore: cast_nullable_to_non_nullable
|
||||
as double,y3: null == y3 ? _self.y3 : y3 // ignore: cast_nullable_to_non_nullable
|
||||
as double,x4: null == x4 ? _self.x4 : x4 // ignore: cast_nullable_to_non_nullable
|
||||
as double,y4: null == y4 ? _self.y4 : y4 // ignore: cast_nullable_to_non_nullable
|
||||
as double,boxScore: null == boxScore ? _self.boxScore : boxScore // ignore: cast_nullable_to_non_nullable
|
||||
as double,textScore: null == textScore ? _self.textScore : textScore // ignore: cast_nullable_to_non_nullable
|
||||
as double,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
|
||||
as String,isVisible: null == isVisible ? _self.isVisible : isVisible // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [Ocr].
|
||||
extension OcrPatterns on Ocr {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Ocr value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _Ocr() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Ocr value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _Ocr():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Ocr value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _Ocr() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String assetId, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double boxScore, double textScore, String text, bool isVisible)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Ocr() when $default != null:
|
||||
return $default(_that.id,_that.assetId,_that.x1,_that.y1,_that.x2,_that.y2,_that.x3,_that.y3,_that.x4,_that.y4,_that.boxScore,_that.textScore,_that.text,_that.isVisible);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String assetId, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double boxScore, double textScore, String text, bool isVisible) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Ocr():
|
||||
return $default(_that.id,_that.assetId,_that.x1,_that.y1,_that.x2,_that.y2,_that.x3,_that.y3,_that.x4,_that.y4,_that.boxScore,_that.textScore,_that.text,_that.isVisible);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String assetId, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double boxScore, double textScore, String text, bool isVisible)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Ocr() when $default != null:
|
||||
return $default(_that.id,_that.assetId,_that.x1,_that.y1,_that.x2,_that.y2,_that.x3,_that.y3,_that.x4,_that.y4,_that.boxScore,_that.textScore,_that.text,_that.isVisible);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _Ocr implements Ocr {
|
||||
const _Ocr({required this.id, required this.assetId, required this.x1, required this.y1, required this.x2, required this.y2, required this.x3, required this.y3, required this.x4, required this.y4, required this.boxScore, required this.textScore, required this.text, required this.isVisible});
|
||||
|
||||
|
||||
@override final String id;
|
||||
@override final String assetId;
|
||||
@override final double x1;
|
||||
@override final double y1;
|
||||
@override final double x2;
|
||||
@override final double y2;
|
||||
@override final double x3;
|
||||
@override final double y3;
|
||||
@override final double x4;
|
||||
@override final double y4;
|
||||
@override final double boxScore;
|
||||
@override final double textScore;
|
||||
@override final String text;
|
||||
@override final bool isVisible;
|
||||
|
||||
/// Create a copy of Ocr
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$OcrCopyWith<_Ocr> get copyWith => __$OcrCopyWithImpl<_Ocr>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Ocr&&(identical(other.id, id) || other.id == id)&&(identical(other.assetId, assetId) || other.assetId == assetId)&&(identical(other.x1, x1) || other.x1 == x1)&&(identical(other.y1, y1) || other.y1 == y1)&&(identical(other.x2, x2) || other.x2 == x2)&&(identical(other.y2, y2) || other.y2 == y2)&&(identical(other.x3, x3) || other.x3 == x3)&&(identical(other.y3, y3) || other.y3 == y3)&&(identical(other.x4, x4) || other.x4 == x4)&&(identical(other.y4, y4) || other.y4 == y4)&&(identical(other.boxScore, boxScore) || other.boxScore == boxScore)&&(identical(other.textScore, textScore) || other.textScore == textScore)&&(identical(other.text, text) || other.text == text)&&(identical(other.isVisible, isVisible) || other.isVisible == isVisible));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,assetId,x1,y1,x2,y2,x3,y3,x4,y4,boxScore,textScore,text,isVisible);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Ocr(id: $id, assetId: $assetId, x1: $x1, y1: $y1, x2: $x2, y2: $y2, x3: $x3, y3: $y3, x4: $x4, y4: $y4, boxScore: $boxScore, textScore: $textScore, text: $text, isVisible: $isVisible)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$OcrCopyWith<$Res> implements $OcrCopyWith<$Res> {
|
||||
factory _$OcrCopyWith(_Ocr value, $Res Function(_Ocr) _then) = __$OcrCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String assetId, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double boxScore, double textScore, String text, bool isVisible
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$OcrCopyWithImpl<$Res>
|
||||
implements _$OcrCopyWith<$Res> {
|
||||
__$OcrCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _Ocr _self;
|
||||
final $Res Function(_Ocr) _then;
|
||||
|
||||
/// Create a copy of Ocr
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? assetId = null,Object? x1 = null,Object? y1 = null,Object? x2 = null,Object? y2 = null,Object? x3 = null,Object? y3 = null,Object? x4 = null,Object? y4 = null,Object? boxScore = null,Object? textScore = null,Object? text = null,Object? isVisible = null,}) {
|
||||
return _then(_Ocr(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,assetId: null == assetId ? _self.assetId : assetId // ignore: cast_nullable_to_non_nullable
|
||||
as String,x1: null == x1 ? _self.x1 : x1 // ignore: cast_nullable_to_non_nullable
|
||||
as double,y1: null == y1 ? _self.y1 : y1 // ignore: cast_nullable_to_non_nullable
|
||||
as double,x2: null == x2 ? _self.x2 : x2 // ignore: cast_nullable_to_non_nullable
|
||||
as double,y2: null == y2 ? _self.y2 : y2 // ignore: cast_nullable_to_non_nullable
|
||||
as double,x3: null == x3 ? _self.x3 : x3 // ignore: cast_nullable_to_non_nullable
|
||||
as double,y3: null == y3 ? _self.y3 : y3 // ignore: cast_nullable_to_non_nullable
|
||||
as double,x4: null == x4 ? _self.x4 : x4 // ignore: cast_nullable_to_non_nullable
|
||||
as double,y4: null == y4 ? _self.y4 : y4 // ignore: cast_nullable_to_non_nullable
|
||||
as double,boxScore: null == boxScore ? _self.boxScore : boxScore // ignore: cast_nullable_to_non_nullable
|
||||
as double,textScore: null == textScore ? _self.textScore : textScore // ignore: cast_nullable_to_non_nullable
|
||||
as double,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
|
||||
as String,isVisible: null == isVisible ? _self.isVisible : isVisible // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,16 +1,26 @@
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset_edit.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/repositories/remote_exif.repository.dart';
|
||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||
import 'package:immich_mobile/utils/option.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
|
||||
class AssetService {
|
||||
final RemoteAssetRepository _remoteRepository;
|
||||
final RemoteExifRepository _exifRepository;
|
||||
final DriftLocalAssetRepository _localRepository;
|
||||
final AssetApiRepository _apiRepository;
|
||||
|
||||
const AssetService({required this._remoteRepository, required this._localRepository, required this._apiRepository});
|
||||
const AssetService({
|
||||
required this._remoteRepository,
|
||||
required this._exifRepository,
|
||||
required this._localRepository,
|
||||
required this._apiRepository,
|
||||
});
|
||||
|
||||
Future<BaseAsset?> getAsset(BaseAsset asset) {
|
||||
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id;
|
||||
@@ -26,10 +36,6 @@ class AssetService {
|
||||
return _localRepository.getByChecksum(checksum);
|
||||
}
|
||||
|
||||
Future<LocalAsset?> getLocalAsset(String id) {
|
||||
return _localRepository.get(id);
|
||||
}
|
||||
|
||||
Future<RemoteAsset?> getRemoteAssetByChecksum(String checksum) {
|
||||
return _remoteRepository.getByChecksum(checksum);
|
||||
}
|
||||
@@ -73,6 +79,99 @@ class AssetService {
|
||||
return _localRepository.getSourceAlbums(localAssetId, backupSelection: backupSelection);
|
||||
}
|
||||
|
||||
Future<void> restoreTrash(List<String> remoteIds) async {
|
||||
if (remoteIds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _apiRepository.restoreTrash(remoteIds);
|
||||
await _remoteRepository.restoreTrash(remoteIds);
|
||||
}
|
||||
|
||||
Future<void> stack(String userId, List<String> remoteIds) async {
|
||||
if (remoteIds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final stack = await _apiRepository.stack(remoteIds);
|
||||
await _remoteRepository.stack(userId, stack);
|
||||
}
|
||||
|
||||
Future<void> unstack(List<String> stackIds) async {
|
||||
if (stackIds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _remoteRepository.unStack(stackIds);
|
||||
await _apiRepository.unStack(stackIds);
|
||||
}
|
||||
|
||||
Future<void> update(
|
||||
List<String> remoteIds, {
|
||||
Option<bool> isFavorite = const .none(),
|
||||
Option<AssetVisibility> visibility = const .none(),
|
||||
Option<LatLng> location = const .none(),
|
||||
Option<String> dateTime = const .none(),
|
||||
}) async {
|
||||
if (remoteIds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final parsedDateTime = dateTime.map((dt) => DateTime.parse(dt));
|
||||
final offset = RegExp(r'[+-]\d{2}:\d{2}$').firstMatch(dateTime.unwrapOrNull ?? '')?.group(0);
|
||||
|
||||
await _apiRepository.update(
|
||||
remoteIds,
|
||||
isFavorite: isFavorite,
|
||||
visibility: visibility,
|
||||
location: location,
|
||||
dateTimeOriginal: dateTime,
|
||||
);
|
||||
await _remoteRepository.update(
|
||||
remoteIds,
|
||||
isFavorite: isFavorite,
|
||||
visibility: visibility,
|
||||
createdAt: parsedDateTime,
|
||||
);
|
||||
await _exifRepository.update(
|
||||
remoteIds,
|
||||
location: location,
|
||||
dateTimeOriginal: parsedDateTime,
|
||||
timeZone: .fromNullable(offset).map((o) => 'UTC$o'),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> trash(List<String> remoteIds) async {
|
||||
if (remoteIds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _apiRepository.delete(remoteIds, false);
|
||||
await _remoteRepository.trash(remoteIds);
|
||||
}
|
||||
|
||||
Future<void> delete(List<String> remoteIds) async {
|
||||
if (remoteIds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _apiRepository.delete(remoteIds, true);
|
||||
await _remoteRepository.delete(remoteIds);
|
||||
}
|
||||
|
||||
Future<void> applyEdits(String remoteId, List<AssetEdit> edits) async {
|
||||
if (edits.isEmpty) {
|
||||
await _apiRepository.removeEdits(remoteId);
|
||||
} else {
|
||||
await _apiRepository.editAsset(remoteId, edits);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(shenlong): remove after action migration
|
||||
Future<LocalAsset?> getLocalAsset(String id) {
|
||||
return _localRepository.get(id);
|
||||
}
|
||||
|
||||
Future<void> updateFavorite(List<String> remoteIds, bool isFavorite) async {
|
||||
if (remoteIds.isEmpty) {
|
||||
return;
|
||||
|
||||
@@ -341,4 +341,10 @@ class RemoteAlbumService {
|
||||
|
||||
return sortedAlbums;
|
||||
}
|
||||
|
||||
Future<int> removeAssets({required String albumId, required List<String> assetIds}) async {
|
||||
final result = await _albumApiRepository.removeAssets(albumId, assetIds);
|
||||
await _repository.removeAssets(albumId, result.removed);
|
||||
return result.removed.length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,13 +72,7 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
|
||||
final query = _db.remoteAssetEntity.select()
|
||||
..where(
|
||||
(row) =>
|
||||
row.stackId.equals(stackId) &
|
||||
row.id.equals(asset.id).not() &
|
||||
row.deletedAt.isNull() &
|
||||
row.visibility.equalsValue(AssetVisibility.timeline),
|
||||
)
|
||||
..where((row) => row.stackId.equals(stackId) & row.id.equals(asset.id).not())
|
||||
..orderBy([(row) => OrderingTerm.desc(row.createdAt)]);
|
||||
|
||||
return query.map((row) => row.toDto()).get();
|
||||
@@ -189,38 +183,6 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> updateLocation(List<String> ids, LatLng location) {
|
||||
return _db.batch((batch) async {
|
||||
for (final id in ids) {
|
||||
batch.update(
|
||||
_db.remoteExifEntity,
|
||||
RemoteExifEntityCompanion(latitude: Value(location.latitude), longitude: Value(location.longitude)),
|
||||
where: (e) => e.assetId.equals(id),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> updateDateTime(List<String> ids, DateTime dateTime, {String? timeZone}) {
|
||||
return _db.batch((batch) async {
|
||||
for (final id in ids) {
|
||||
batch.update(
|
||||
_db.remoteExifEntity,
|
||||
RemoteExifEntityCompanion(
|
||||
dateTimeOriginal: Value(dateTime),
|
||||
timeZone: timeZone == null ? const Value.absent() : Value(timeZone),
|
||||
),
|
||||
where: (e) => e.assetId.equals(id),
|
||||
);
|
||||
batch.update(
|
||||
_db.remoteAssetEntity,
|
||||
RemoteAssetEntityCompanion(createdAt: Value(dateTime)),
|
||||
where: (e) => e.id.equals(id),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> stack(String userId, StackResponse stack) {
|
||||
return _db.transaction(() async {
|
||||
final stackIds = await _db.managers.stackEntity
|
||||
@@ -298,10 +260,16 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
|
||||
List<String> remoteIds, {
|
||||
Option<bool> isFavorite = const .none(),
|
||||
Option<AssetVisibility> visibility = const .none(),
|
||||
}) {
|
||||
Option<DateTime> createdAt = const .none(),
|
||||
}) async {
|
||||
if ([isFavorite, visibility, createdAt].every((option) => option.isNone)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final companion = RemoteAssetEntityCompanion(
|
||||
visibility: visibility.toDriftValue(),
|
||||
isFavorite: isFavorite.toDriftValue(),
|
||||
createdAt: createdAt.toDriftValue(),
|
||||
);
|
||||
return _db.batch((batch) {
|
||||
for (final remoteId in remoteIds) {
|
||||
@@ -309,4 +277,37 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(shenlong): remove after action migration
|
||||
Future<void> updateLocation(List<String> ids, LatLng location) {
|
||||
return _db.batch((batch) async {
|
||||
for (final id in ids) {
|
||||
batch.update(
|
||||
_db.remoteExifEntity,
|
||||
RemoteExifEntityCompanion(latitude: Value(location.latitude), longitude: Value(location.longitude)),
|
||||
where: (e) => e.assetId.equals(id),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> updateDateTime(List<String> ids, DateTime dateTime, {String? timeZone}) {
|
||||
return _db.batch((batch) async {
|
||||
for (final id in ids) {
|
||||
batch.update(
|
||||
_db.remoteExifEntity,
|
||||
RemoteExifEntityCompanion(
|
||||
dateTimeOriginal: Value(dateTime),
|
||||
timeZone: timeZone == null ? const Value.absent() : Value(timeZone),
|
||||
),
|
||||
where: (e) => e.assetId.equals(id),
|
||||
);
|
||||
batch.update(
|
||||
_db.remoteAssetEntity,
|
||||
RemoteAssetEntityCompanion(createdAt: Value(dateTime)),
|
||||
where: (e) => e.id.equals(id),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/utils/option.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
|
||||
class RemoteExifRepository extends DriftDatabaseRepository {
|
||||
final Drift _db;
|
||||
|
||||
const RemoteExifRepository(this._db) : super(_db);
|
||||
|
||||
Future<void> update(
|
||||
List<String> ids, {
|
||||
Option<DateTime> dateTimeOriginal = const .none(),
|
||||
Option<String> timeZone = const .none(),
|
||||
Option<LatLng> location = const .none(),
|
||||
}) async {
|
||||
if ([dateTimeOriginal, timeZone, location].every((option) => option.isNone)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final companion = RemoteExifEntityCompanion(
|
||||
dateTimeOriginal: dateTimeOriginal.toDriftValue(),
|
||||
timeZone: timeZone.toDriftValue(),
|
||||
latitude: location.map((loc) => loc.latitude).toDriftValue(),
|
||||
longitude: location.map((loc) => loc.longitude).toDriftValue(),
|
||||
);
|
||||
|
||||
return _db.batch((batch) {
|
||||
for (final id in ids) {
|
||||
batch.update(_db.remoteExifEntity, companion, where: (a) => a.assetId.equals(id));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ class _ActionWidget extends ConsumerWidget {
|
||||
try {
|
||||
await action.onAction(scope);
|
||||
} catch (error, stackTrace) {
|
||||
handleError(scope.context, error, stack: stackTrace, description: 'Action failed: ${action.runtimeType}');
|
||||
handleError(scope.context, stack: stackTrace, description: 'Action failed: ${action.runtimeType}');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/services/asset.service.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/repositories/remote_exif.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
@@ -15,6 +16,8 @@ final remoteAssetRepositoryProvider = Provider<RemoteAssetRepository>(
|
||||
(ref) => RemoteAssetRepository(ref.watch(driftProvider)),
|
||||
);
|
||||
|
||||
final remoteExifRepositoryProvider = Provider((ref) => RemoteExifRepository(ref.watch(driftProvider)));
|
||||
|
||||
final trashedLocalAssetRepository = Provider<DriftTrashedLocalAssetRepository>(
|
||||
(ref) => DriftTrashedLocalAssetRepository(ref.watch(driftProvider)),
|
||||
);
|
||||
@@ -22,6 +25,7 @@ final trashedLocalAssetRepository = Provider<DriftTrashedLocalAssetRepository>(
|
||||
final assetServiceProvider = Provider(
|
||||
(ref) => AssetService(
|
||||
remoteRepository: ref.watch(remoteAssetRepositoryProvider),
|
||||
exifRepository: ref.watch(remoteExifRepositoryProvider),
|
||||
localRepository: ref.watch(localAssetRepository),
|
||||
apiRepository: ref.watch(assetApiRepositoryProvider),
|
||||
),
|
||||
|
||||
@@ -43,28 +43,11 @@ class AssetApiRepository extends ApiRepository {
|
||||
return response?.count ?? 0;
|
||||
}
|
||||
|
||||
// TODO(shenlong): remove after action migration
|
||||
Future<void> updateVisibility(List<String> ids, AssetVisibility visibility) async {
|
||||
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, visibility: Optional.present(_mapVisibility(visibility))));
|
||||
}
|
||||
|
||||
Future<void> updateFavorite(List<String> ids, bool isFavorite) async {
|
||||
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, isFavorite: Optional.present(isFavorite)));
|
||||
}
|
||||
|
||||
Future<void> updateLocation(List<String> ids, LatLng location) async {
|
||||
return _api.updateAssets(
|
||||
AssetBulkUpdateDto(
|
||||
ids: ids,
|
||||
latitude: Optional.present(location.latitude),
|
||||
longitude: Optional.present(location.longitude),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateDateTime(List<String> ids, String dateTime) async {
|
||||
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, dateTimeOriginal: Optional.present(dateTime)));
|
||||
}
|
||||
|
||||
Future<StackResponse> stack(List<String> ids) async {
|
||||
final responseDto = await checkNull(_stacksApi.createStack(StackCreateDto(assetIds: ids)));
|
||||
|
||||
@@ -113,15 +96,39 @@ class AssetApiRepository extends ApiRepository {
|
||||
List<String> remoteIds, {
|
||||
Option<bool> isFavorite = const .none(),
|
||||
Option<AssetVisibility> visibility = const .none(),
|
||||
Option<String> dateTimeOriginal = const .none(),
|
||||
Option<LatLng> location = const .none(),
|
||||
}) {
|
||||
return _api.updateAssets(
|
||||
AssetBulkUpdateDto(
|
||||
ids: remoteIds,
|
||||
isFavorite: isFavorite.toOptional(),
|
||||
visibility: visibility.map(_mapVisibility).toOptional(),
|
||||
dateTimeOriginal: dateTimeOriginal.toOptional(),
|
||||
latitude: location.map((loc) => loc.latitude).toOptional(),
|
||||
longitude: location.map((loc) => loc.longitude).toOptional(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(shenlong): remove after action migration
|
||||
Future<void> updateFavorite(List<String> ids, bool isFavorite) async {
|
||||
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, isFavorite: Optional.present(isFavorite)));
|
||||
}
|
||||
|
||||
Future<void> updateLocation(List<String> ids, LatLng location) async {
|
||||
return _api.updateAssets(
|
||||
AssetBulkUpdateDto(
|
||||
ids: ids,
|
||||
latitude: Optional.present(location.latitude),
|
||||
longitude: Optional.present(location.longitude),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateDateTime(List<String> ids, String dateTime) async {
|
||||
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, dateTimeOriginal: Optional.present(dateTime)));
|
||||
}
|
||||
}
|
||||
|
||||
extension on StackResponseDto {
|
||||
|
||||
@@ -15,8 +15,10 @@ extension type const AssetFilter<T extends BaseAsset>(Iterable<T> assets) implem
|
||||
remote().where((asset) => asset.visibility != visibility);
|
||||
AssetFilter<RemoteAsset> archived({bool isArchived = true}) =>
|
||||
remote().where((asset) => asset.isArchived == isArchived);
|
||||
AssetFilter<RemoteAsset> locked({bool isLocked = true}) => remote().where((asset) => asset.isLocked == isLocked);
|
||||
AssetFilter<RemoteAsset> stacked({bool isStacked = true}) => remote().where((asset) => asset.isStacked == isStacked);
|
||||
AssetFilter<RemoteAsset> trashed({bool isTrashed = true}) => remote().where((asset) => asset.isTrashed == isTrashed);
|
||||
|
||||
AssetFilter<LocalAsset> local() => AssetFilter(assets.whereType<LocalAsset>());
|
||||
AssetFilter<LocalAsset> backedUp() => local().where((asset) => asset.remoteAssetId != null);
|
||||
AssetFilter<BaseAsset> backedUp({bool isBackedUp = true}) => where((asset) => asset.isMerged == isBackedUp);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:openapi/api.dart';
|
||||
// ignore: depend_on_referenced_packages
|
||||
import 'package:stack_trace/stack_trace.dart';
|
||||
|
||||
void handleError(BuildContext context, Object error, {StackTrace? stack, String? description}) {
|
||||
void handleError(Object error, {StackTrace? stack, String? description}) {
|
||||
String? stackTrace;
|
||||
if (stack != null) {
|
||||
final trace = Trace.from(stack);
|
||||
@@ -23,17 +23,13 @@ void handleError(BuildContext context, Object error, {StackTrace? stack, String?
|
||||
() => 'Error${description != null ? ' ($description)' : ''}: $error${stackTrace != null ? '\n$stackTrace' : ''}',
|
||||
);
|
||||
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String message;
|
||||
if (serverErrorMessage(error) case String serverMessage) {
|
||||
message = serverMessage;
|
||||
} else if (isConnectionError(error)) {
|
||||
message = context.t.login_form_server_error;
|
||||
message = StaticTranslations.instance.login_form_server_error;
|
||||
} else {
|
||||
message = context.t.scaffold_body_error_occurred;
|
||||
message = StaticTranslations.instance.scaffold_body_error_occurred;
|
||||
}
|
||||
|
||||
snackbar.error(message);
|
||||
|
||||
@@ -32,17 +32,6 @@ sealed class Option<T> {
|
||||
None() => onNone(),
|
||||
};
|
||||
|
||||
Option<U> flatMap<U>(Option<U> Function(T value) f) => switch (this) {
|
||||
Some(:final value) => f(value),
|
||||
None() => const Option.none(),
|
||||
};
|
||||
|
||||
void ifPresent(void Function(T value) f) {
|
||||
if (this case Some(:final value)) {
|
||||
f(value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => switch (this) {
|
||||
Some(:final value) => 'Some($value)',
|
||||
|
||||
@@ -667,22 +667,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.2.14"
|
||||
freezed:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: freezed
|
||||
sha256: f23ea33b3863f119b58ed1b586e881a46bd28715ddcc4dbc33104524e3434131
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.5"
|
||||
freezed_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: freezed_annotation
|
||||
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -91,7 +91,6 @@ dependencies:
|
||||
url: https://github.com/mertalev/http
|
||||
ref: '549c24b0a4d3881a9a44b70f4873450d43c1c4af' # https://github.com/dart-lang/http/pull/1877
|
||||
path: pkgs/ok_http/
|
||||
freezed_annotation: ^3.1.0
|
||||
|
||||
dev_dependencies:
|
||||
auto_route_generator: ^10.5.0
|
||||
@@ -105,7 +104,6 @@ dev_dependencies:
|
||||
flutter_native_splash: ^2.4.7
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
freezed: ^3.2.5
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
mocktail: ^1.0.5
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import 'package:immich_mobile/domain/services/asset.service.dart';
|
||||
import 'package:immich_mobile/domain/services/partner.service.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/domain/services/user.service.dart';
|
||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockStoreService extends Mock implements StoreService {}
|
||||
|
||||
class MockBackgroundSyncManager extends Mock implements BackgroundSyncManager {}
|
||||
|
||||
class MockNativeSyncApi extends Mock implements NativeSyncApi {}
|
||||
|
||||
class MockAppSettingsService extends Mock implements AppSettingsService {}
|
||||
|
||||
class MockPartnerService extends Mock implements PartnerService {}
|
||||
|
||||
class MockAssetService extends Mock implements AssetService {}
|
||||
|
||||
class MockUserService extends Mock implements UserService {}
|
||||
@@ -16,10 +16,10 @@ import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../../domain/service.mock.dart';
|
||||
import '../../fixtures/asset.stub.dart';
|
||||
import '../../infrastructure/repository.mock.dart';
|
||||
import '../../repository.mocks.dart';
|
||||
import '../../service.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late LocalSyncService sut;
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||
|
||||
import '../../infrastructure/repository.mock.dart';
|
||||
import '../service.mock.dart';
|
||||
import '../../service.mocks.dart';
|
||||
|
||||
void main() {
|
||||
// A container with the service's deps overridden but cancellationProvider left
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../../fixtures/user.stub.dart';
|
||||
import '../../infrastructure/repository.mock.dart';
|
||||
import '../service.mock.dart';
|
||||
import '../../service.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late UserService sut;
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
|
||||
import '../../../fixtures/asset.stub.dart';
|
||||
import '../../../unit/presentation/presentation_context.dart';
|
||||
|
||||
class _SeededAssetViewerNotifier extends AssetViewerStateNotifier {
|
||||
@override
|
||||
@@ -32,6 +33,12 @@ TimelineService _stubTimelineService() {
|
||||
}
|
||||
|
||||
void main() {
|
||||
late PresentationContext context;
|
||||
|
||||
setUp(() async {
|
||||
context = await PresentationContext.create();
|
||||
});
|
||||
|
||||
testWidgets('status bar icons are light while the asset viewer is open in light mode', (tester) async {
|
||||
// Emulate arriving from a light-themed page whose AppBar set dark status
|
||||
// bar icons (the state the viewer is opened from in light mode).
|
||||
@@ -48,6 +55,7 @@ void main() {
|
||||
assetLoader: const CodegenLoader(),
|
||||
child: ProviderScope(
|
||||
overrides: [
|
||||
...context.overrides,
|
||||
timelineServiceProvider.overrideWithValue(_stubTimelineService()),
|
||||
assetViewerProvider.overrideWith(_SeededAssetViewerNotifier.new),
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../../domain/service.mock.dart';
|
||||
import '../../service.mocks.dart';
|
||||
import '../../unit/factories/remote_asset_factory.dart';
|
||||
|
||||
void main() {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'package:immich_mobile/domain/services/tag.service.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/remote_exif.repository.dart';
|
||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||
import 'package:immich_mobile/repositories/auth.repository.dart';
|
||||
import 'package:immich_mobile/repositories/auth_api.repository.dart';
|
||||
import 'package:immich_mobile/domain/services/tag.service.dart';
|
||||
import 'package:immich_mobile/repositories/download.repository.dart';
|
||||
import 'package:immich_mobile/repositories/permission.repository.dart';
|
||||
import 'package:immich_mobile/repositories/toast.repository.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockAssetApiRepository extends Mock implements AssetApiRepository {}
|
||||
@@ -17,3 +20,9 @@ class MockAuthApiRepository extends Mock implements AuthApiRepository {}
|
||||
class MockAuthRepository extends Mock implements AuthRepository {}
|
||||
|
||||
class MockTagService extends Mock implements TagService {}
|
||||
|
||||
class MockDownloadRepository extends Mock implements DownloadRepository {}
|
||||
|
||||
class MockRemoteExifRepository extends Mock implements RemoteExifRepository {}
|
||||
|
||||
class MockToastRepository extends Mock implements ToastRepository {}
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import 'package:immich_mobile/domain/services/asset.service.dart';
|
||||
import 'package:immich_mobile/domain/services/partner.service.dart';
|
||||
import 'package:immich_mobile/domain/services/remote_album.service.dart';
|
||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/domain/services/user.service.dart';
|
||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/services/cleanup.service.dart';
|
||||
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
||||
import 'package:immich_mobile/services/gcast.service.dart';
|
||||
import 'package:immich_mobile/services/network.service.dart';
|
||||
import 'package:immich_mobile/services/server_info.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockApiService extends Mock implements ApiService {}
|
||||
@@ -8,3 +19,27 @@ class MockApiService extends Mock implements ApiService {}
|
||||
class MockNetworkService extends Mock implements NetworkService {}
|
||||
|
||||
class MockAppSettingService extends Mock implements AppSettingsService {}
|
||||
|
||||
class MockStoreService extends Mock implements StoreService {}
|
||||
|
||||
class MockNativeSyncApi extends Mock implements NativeSyncApi {}
|
||||
|
||||
class MockAppSettingsService extends Mock implements AppSettingsService {}
|
||||
|
||||
class MockPartnerService extends Mock implements PartnerService {}
|
||||
|
||||
class MockAssetService extends Mock implements AssetService {}
|
||||
|
||||
class MockUserService extends Mock implements UserService {}
|
||||
|
||||
class MockRemoteAlbumService extends Mock implements RemoteAlbumService {}
|
||||
|
||||
class MockGCastService extends Mock implements GCastService {}
|
||||
|
||||
class MockForegroundUploadService extends Mock implements ForegroundUploadService {}
|
||||
|
||||
class MockServerInfoService extends Mock implements ServerInfoService {}
|
||||
|
||||
class MockCleanupService extends Mock implements CleanupService {}
|
||||
|
||||
class MockBackgroundSyncManager extends Mock implements BackgroundSyncManager {}
|
||||
|
||||
@@ -7,15 +7,12 @@ import 'package:immich_mobile/domain/services/store.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/repositories/download.repository.dart';
|
||||
import 'package:immich_mobile/services/action.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../infrastructure/repository.mock.dart';
|
||||
import '../repository.mocks.dart';
|
||||
|
||||
class MockDownloadRepository extends Mock implements DownloadRepository {}
|
||||
|
||||
void main() {
|
||||
late ActionService sut;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import 'package:immich_mobile/services/auth.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
import '../domain/service.mock.dart';
|
||||
import '../repository.mocks.dart';
|
||||
import '../service.mocks.dart';
|
||||
|
||||
@@ -29,13 +28,7 @@ void main() {
|
||||
apiService = MockApiService();
|
||||
networkService = MockNetworkService();
|
||||
backgroundSyncManager = MockBackgroundSyncManager();
|
||||
sut = AuthService(
|
||||
authApiRepository,
|
||||
authRepository,
|
||||
apiService,
|
||||
networkService,
|
||||
backgroundSyncManager,
|
||||
);
|
||||
sut = AuthService(authApiRepository, authRepository, apiService, networkService, backgroundSyncManager);
|
||||
|
||||
registerFallbackValue(Uri());
|
||||
});
|
||||
|
||||
@@ -5,12 +5,13 @@ import '../../utils.dart';
|
||||
class LocalAssetFactory {
|
||||
const LocalAssetFactory();
|
||||
|
||||
static LocalAsset create({String? id, String? name}) {
|
||||
static LocalAsset create({String? id, String? name, String? remoteId}) {
|
||||
id = TestUtils.uuid(id);
|
||||
|
||||
return LocalAsset(
|
||||
id: id,
|
||||
name: name ?? 'local_$id.jpg',
|
||||
remoteId: remoteId,
|
||||
type: AssetType.image,
|
||||
createdAt: TestUtils.yesterday(),
|
||||
updatedAt: TestUtils.now(),
|
||||
|
||||
38
mobile/test/unit/factories/remote_album_factory.dart
Normal file
38
mobile/test/unit/factories/remote_album_factory.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
|
||||
import '../../utils.dart';
|
||||
|
||||
class RemoteAlbumFactory {
|
||||
const RemoteAlbumFactory();
|
||||
|
||||
static RemoteAlbum create({
|
||||
String? id,
|
||||
String? name,
|
||||
String? ownerId,
|
||||
String? description,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
String? thumbnailAssetId,
|
||||
bool isActivityEnabled = false,
|
||||
AlbumAssetOrder order = AlbumAssetOrder.desc,
|
||||
int assetCount = 0,
|
||||
String? ownerName,
|
||||
bool isShared = false,
|
||||
}) {
|
||||
id = TestUtils.uuid(id);
|
||||
return RemoteAlbum(
|
||||
id: id,
|
||||
name: name ?? 'remote_album_$id',
|
||||
ownerId: TestUtils.uuid(ownerId),
|
||||
description: description ?? '',
|
||||
createdAt: TestUtils.date(createdAt),
|
||||
updatedAt: TestUtils.date(updatedAt),
|
||||
thumbnailAssetId: thumbnailAssetId,
|
||||
isActivityEnabled: isActivityEnabled,
|
||||
order: order,
|
||||
assetCount: assetCount,
|
||||
ownerName: ownerName ?? 'owner_$id',
|
||||
isShared: isShared,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,11 @@ class RemoteAssetFactory {
|
||||
String? name,
|
||||
String? ownerId,
|
||||
bool isFavorite = false,
|
||||
AssetVisibility visibility = AssetVisibility.timeline,
|
||||
AssetVisibility visibility = .timeline,
|
||||
AssetType type = .image,
|
||||
String? stackId,
|
||||
DateTime? deletedAt,
|
||||
String? localId,
|
||||
}) {
|
||||
id = TestUtils.uuid(id);
|
||||
|
||||
@@ -20,13 +23,15 @@ class RemoteAssetFactory {
|
||||
name: name ?? 'remote_$id.jpg',
|
||||
ownerId: TestUtils.uuid(ownerId),
|
||||
checksum: 'checksum-$id',
|
||||
type: .image,
|
||||
type: type,
|
||||
createdAt: TestUtils.yesterday(),
|
||||
updatedAt: TestUtils.now(),
|
||||
isFavorite: isFavorite,
|
||||
visibility: visibility,
|
||||
stackId: stackId,
|
||||
isEdited: false,
|
||||
deletedAt: deletedAt,
|
||||
localId: localId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,43 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset_edit.model.dart';
|
||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
import 'package:immich_mobile/domain/models/tag.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
||||
import 'package:immich_mobile/utils/option.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
import 'package:mocktail/mocktail.dart' as mock;
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../domain/service.mock.dart';
|
||||
import '../infrastructure/repository.mock.dart';
|
||||
import '../repository.mocks.dart';
|
||||
import '../service.mocks.dart';
|
||||
import 'factories/local_album_factory.dart';
|
||||
import 'factories/local_asset_factory.dart';
|
||||
import 'factories/remote_album_factory.dart';
|
||||
import 'factories/user_factory.dart';
|
||||
|
||||
class RepositoryMocks {
|
||||
final localAlbum = LocalAlbumRepositoryStub(MockLocalAlbumRepository());
|
||||
final localAsset = LocalAssetRepositoryStub(MockDriftLocalAssetRepository());
|
||||
final remoteAsset = RemoteAssetRepositoryStub(MockRemoteAssetRepository());
|
||||
final remoteExif = RemoteExifRepositoryStub(MockRemoteExifRepository());
|
||||
final trashedAsset = MockTrashedLocalAssetRepository();
|
||||
final toast = MockToastRepository();
|
||||
final remoteAlbum = MockRemoteAlbumRepository();
|
||||
final albumApi = MockDriftAlbumApiRepository();
|
||||
|
||||
final nativeApi = NativeSyncApiStub(MockNativeSyncApi());
|
||||
final assetApi = AssetApiRepositoryStub(MockAssetApiRepository());
|
||||
final assetMedia = AssetMediaRepositoryStub(MockAssetMediaRepository());
|
||||
final download = DownloadRepositoryStub(MockDownloadRepository());
|
||||
|
||||
RepositoryMocks() {
|
||||
resetAll();
|
||||
@@ -29,11 +47,34 @@ class RepositoryMocks {
|
||||
_registerFallbacks();
|
||||
localAlbum.reset();
|
||||
localAsset.reset();
|
||||
remoteAsset.reset();
|
||||
remoteExif.reset();
|
||||
reset(trashedAsset);
|
||||
reset(remoteAlbum);
|
||||
reset(albumApi);
|
||||
nativeApi.reset();
|
||||
assetApi.reset();
|
||||
assetMedia.reset();
|
||||
download.reset();
|
||||
reset(toast);
|
||||
_stubLocalAlbumRepository();
|
||||
_stubLocalAssetRepository();
|
||||
_stubRemoteAssetRepository();
|
||||
_stubRemoteExifRepository();
|
||||
_stubNativeSyncApi();
|
||||
_stubAssetApiRepository();
|
||||
_stubAssetMediaRepository();
|
||||
_stubDownloadRepository();
|
||||
}
|
||||
|
||||
void _stubRemoteAssetRepository() {
|
||||
when(remoteAsset.getExif).thenAnswer((_) async => null);
|
||||
when(remoteAsset.getAssetEdits).thenAnswer((_) async => const []);
|
||||
when(remoteAsset.update).thenAnswer((_) async {});
|
||||
}
|
||||
|
||||
void _stubRemoteExifRepository() {
|
||||
when(remoteExif.update).thenAnswer((_) async {});
|
||||
}
|
||||
|
||||
void _stubLocalAlbumRepository() {
|
||||
@@ -49,12 +90,31 @@ class RepositoryMocks {
|
||||
void _stubNativeSyncApi() {
|
||||
when(nativeApi.hashAssets).thenAnswer((_) async => []);
|
||||
}
|
||||
|
||||
void _stubAssetApiRepository() {
|
||||
when(assetApi.update).thenAnswer((_) async => {});
|
||||
}
|
||||
|
||||
void _stubAssetMediaRepository() {
|
||||
when(assetMedia.shareAssets).thenAnswer((_) async => 1);
|
||||
}
|
||||
|
||||
void _stubDownloadRepository() {
|
||||
when(download.downloadAllAssets).thenAnswer((_) async => const []);
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceMocks {
|
||||
final partner = PartnerServiceStub(MockPartnerService());
|
||||
final user = UserServiceStub(MockUserService());
|
||||
final asset = AssetServiceStub(MockAssetService());
|
||||
final album = RemoteAlbumServiceStub(MockRemoteAlbumService());
|
||||
final cleanup = CleanupServiceStub(MockCleanupService());
|
||||
final tag = TagServiceStub(MockTagService());
|
||||
final backgroundSync = MockBackgroundSyncManager();
|
||||
final upload = MockForegroundUploadService();
|
||||
final cast = MockGCastService();
|
||||
final serverInfo = MockServerInfoService();
|
||||
|
||||
ServiceMocks() {
|
||||
resetAll();
|
||||
@@ -65,9 +125,21 @@ class ServiceMocks {
|
||||
partner.reset();
|
||||
user.reset();
|
||||
asset.reset();
|
||||
album.reset();
|
||||
cleanup.reset();
|
||||
tag.reset();
|
||||
reset(cast);
|
||||
reset(serverInfo);
|
||||
reset(backgroundSync);
|
||||
reset(upload);
|
||||
_stubUserService();
|
||||
_stubPartnerService();
|
||||
_stubAssetService();
|
||||
_stubRemoteAlbumService();
|
||||
_stubCleanupService();
|
||||
_stubTagService();
|
||||
_stubBackgroundSync();
|
||||
_stubForegroundUpload();
|
||||
}
|
||||
|
||||
void _stubUserService() {
|
||||
@@ -88,7 +160,43 @@ class ServiceMocks {
|
||||
}
|
||||
|
||||
void _stubAssetService() {
|
||||
when(asset.updateFavorite).thenAnswer((_) async {});
|
||||
when(asset.update).thenAnswer((_) async {});
|
||||
when(asset.stack).thenAnswer((_) async {});
|
||||
when(asset.unstack).thenAnswer((_) async {});
|
||||
when(asset.restoreTrash).thenAnswer((_) async {});
|
||||
when(asset.trash).thenAnswer((_) async {});
|
||||
when(asset.delete).thenAnswer((_) async {});
|
||||
when(asset.applyEdits).thenAnswer((_) async {});
|
||||
}
|
||||
|
||||
void _stubRemoteAlbumService() {
|
||||
when(album.removeAssets).thenAnswer((_) async => 0);
|
||||
when(album.updateAlbum).thenAnswer((_) async => RemoteAlbumFactory.create());
|
||||
}
|
||||
|
||||
void _stubCleanupService() {
|
||||
when(cleanup.deleteLocalAssets).thenAnswer((_) async => 0);
|
||||
}
|
||||
|
||||
void _stubTagService() {
|
||||
when(tag.bulkTagAssets).thenAnswer((_) async => 0);
|
||||
when(tag.upsertTags).thenAnswer((_) async => const []);
|
||||
when(tag.getAllTags).thenAnswer((_) async => const {});
|
||||
}
|
||||
|
||||
void _stubBackgroundSync() {
|
||||
when(() => backgroundSync.syncLocal()).thenAnswer((_) async {});
|
||||
when(() => backgroundSync.hashAssets()).thenAnswer((_) async {});
|
||||
}
|
||||
|
||||
void _stubForegroundUpload() {
|
||||
when(
|
||||
() => upload.uploadManual(
|
||||
any(),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
callbacks: any(named: 'callbacks'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +204,24 @@ void _registerFallbacks() {
|
||||
registerFallbackValue(LocalAlbumFactory.create());
|
||||
registerFallbackValue(LocalAssetFactory.create());
|
||||
registerFallbackValue(Uint8List(0));
|
||||
registerFallbackValue(AssetVisibility.timeline);
|
||||
registerFallbackValue(const LatLng(0, 0));
|
||||
registerFallbackValue(<AssetEdit>[]);
|
||||
registerFallbackValue(const Option<bool>.none());
|
||||
registerFallbackValue(const Option<AssetVisibility>.none());
|
||||
registerFallbackValue(const Option<LatLng>.none());
|
||||
registerFallbackValue(const Option<String>.none());
|
||||
registerFallbackValue(const Option<DateTime>.none());
|
||||
registerFallbackValue(<BaseAsset>[]);
|
||||
registerFallbackValue(<RemoteAsset>[]);
|
||||
registerFallbackValue(<LocalAsset>[]);
|
||||
registerFallbackValue(ShareAssetType.original);
|
||||
registerFallbackValue(const UploadCallbacks());
|
||||
registerFallbackValue(_FakeBuildContext());
|
||||
}
|
||||
|
||||
class _FakeBuildContext extends Fake implements BuildContext {}
|
||||
|
||||
extension type const Stub<T extends Mock>(T mockedClass) {
|
||||
void reset() => mock.reset(mockedClass);
|
||||
}
|
||||
@@ -119,6 +243,33 @@ extension type const LocalAssetRepositoryStub(MockDriftLocalAssetRepository repo
|
||||
() => repo.updateHashes(any());
|
||||
}
|
||||
|
||||
extension type const RemoteAssetRepositoryStub(MockRemoteAssetRepository repo)
|
||||
implements Stub<MockRemoteAssetRepository> {
|
||||
Future<ExifInfo?> Function() get getExif =>
|
||||
() => repo.getExif(any());
|
||||
|
||||
Future<List<AssetEdit>> Function() get getAssetEdits =>
|
||||
() => repo.getAssetEdits(any());
|
||||
|
||||
Future<void> Function() get update =>
|
||||
() => repo.update(
|
||||
any(),
|
||||
isFavorite: any(named: 'isFavorite'),
|
||||
visibility: any(named: 'visibility'),
|
||||
createdAt: any(named: 'createdAt'),
|
||||
);
|
||||
}
|
||||
|
||||
extension type const RemoteExifRepositoryStub(MockRemoteExifRepository repo) implements Stub<MockRemoteExifRepository> {
|
||||
Future<void> Function() get update =>
|
||||
() => repo.update(
|
||||
any(),
|
||||
dateTimeOriginal: any(named: 'dateTimeOriginal'),
|
||||
timeZone: any(named: 'timeZone'),
|
||||
location: any(named: 'location'),
|
||||
);
|
||||
}
|
||||
|
||||
extension type const PartnerServiceStub(MockPartnerService service) implements Stub<MockPartnerService> {
|
||||
Stream<Iterable<User>> Function() get getCandidates =>
|
||||
() => service.getCandidates(any());
|
||||
@@ -165,11 +316,89 @@ extension type const UserServiceStub(MockUserService service) implements Stub<Mo
|
||||
}
|
||||
|
||||
extension type const AssetServiceStub(MockAssetService service) implements Stub<MockAssetService> {
|
||||
Future<void> Function() get updateFavorite =>
|
||||
() => service.updateFavorite(any(), any());
|
||||
Future<void> Function() get update =>
|
||||
() => service.update(
|
||||
any(),
|
||||
isFavorite: any(named: 'isFavorite'),
|
||||
visibility: any(named: 'visibility'),
|
||||
dateTime: any(named: 'dateTime'),
|
||||
location: any(named: 'location'),
|
||||
);
|
||||
|
||||
Future<void> Function() get stack =>
|
||||
() => service.stack(any(), any());
|
||||
|
||||
Future<void> Function() get unstack =>
|
||||
() => service.unstack(any());
|
||||
|
||||
Future<void> Function() get restoreTrash =>
|
||||
() => service.restoreTrash(any());
|
||||
|
||||
Future<void> Function() get trash =>
|
||||
() => service.trash(any());
|
||||
|
||||
Future<void> Function() get delete =>
|
||||
() => service.delete(any());
|
||||
|
||||
Future<void> Function() get applyEdits =>
|
||||
() => service.applyEdits(any(), any());
|
||||
}
|
||||
|
||||
extension type const RemoteAlbumServiceStub(MockRemoteAlbumService service) implements Stub<MockRemoteAlbumService> {
|
||||
Future<int> Function() get removeAssets =>
|
||||
() => service.removeAssets(
|
||||
albumId: any(named: 'albumId'),
|
||||
assetIds: any(named: 'assetIds'),
|
||||
);
|
||||
|
||||
Future<RemoteAlbum> Function() get updateAlbum =>
|
||||
() => service.updateAlbum(any(), thumbnailAssetId: any(named: 'thumbnailAssetId'));
|
||||
}
|
||||
|
||||
extension type const CleanupServiceStub(MockCleanupService service) implements Stub<MockCleanupService> {
|
||||
Future<int> Function() get deleteLocalAssets =>
|
||||
() => service.deleteLocalAssets(any());
|
||||
}
|
||||
|
||||
extension type const NativeSyncApiStub(MockNativeSyncApi api) implements Stub<MockNativeSyncApi> {
|
||||
Future<List<HashResult>> Function() get hashAssets =>
|
||||
() => api.hashAssets(any(), allowNetworkAccess: any(named: 'allowNetworkAccess'));
|
||||
}
|
||||
|
||||
extension type const AssetApiRepositoryStub(MockAssetApiRepository api) implements Stub<MockAssetApiRepository> {
|
||||
Future<void> Function() get update =>
|
||||
() => api.update(
|
||||
any(),
|
||||
isFavorite: any(named: 'isFavorite'),
|
||||
visibility: any(named: 'visibility'),
|
||||
dateTimeOriginal: any(named: 'dateTimeOriginal'),
|
||||
location: any(named: 'location'),
|
||||
);
|
||||
}
|
||||
|
||||
extension type const AssetMediaRepositoryStub(MockAssetMediaRepository api) implements Stub<MockAssetMediaRepository> {
|
||||
Future<int> Function() get shareAssets =>
|
||||
() => api.shareAssets(
|
||||
any(),
|
||||
any(),
|
||||
fileType: any(named: 'fileType'),
|
||||
cancelCompleter: any(named: 'cancelCompleter'),
|
||||
onAssetDownloadProgress: any(named: 'onAssetDownloadProgress'),
|
||||
);
|
||||
}
|
||||
|
||||
extension type const DownloadRepositoryStub(MockDownloadRepository repo) implements Stub<MockDownloadRepository> {
|
||||
Future<List<bool>> Function() get downloadAllAssets =>
|
||||
() => repo.downloadAllAssets(any());
|
||||
}
|
||||
|
||||
extension type const TagServiceStub(MockTagService service) implements Stub<MockTagService> {
|
||||
Future<int> Function() get bulkTagAssets =>
|
||||
() => service.bulkTagAssets(any(), any());
|
||||
|
||||
Future<List<Tag>> Function() get upsertTags =>
|
||||
() => service.upsertTags(any());
|
||||
|
||||
Future<Set<Tag>> Function() get getAllTags =>
|
||||
() => service.getAllTags();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/presentation/actions/favorite.action.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../../../domain/service.mock.dart';
|
||||
import '../../../service.mocks.dart';
|
||||
import '../../factories/remote_asset_factory.dart';
|
||||
import '../presentation_context.dart';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/presentation/actions/partner.action.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../../../domain/service.mock.dart';
|
||||
import '../../../service.mocks.dart';
|
||||
import '../../factories/user_factory.dart';
|
||||
import '../presentation_context.dart';
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ import 'package:immich_mobile/presentation/actions/action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/services/gcast.service.dart';
|
||||
import 'package:immich_mobile/services/server_info.service.dart';
|
||||
import 'package:immich_ui/immich_ui.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
@@ -29,6 +32,7 @@ class PresentationContext {
|
||||
service = ServiceMocks(),
|
||||
repository = RepositoryMocks() {
|
||||
setup();
|
||||
addTearDown(dispose);
|
||||
}
|
||||
|
||||
static const String serverEndpoint = 'http://localhost:3000';
|
||||
@@ -43,6 +47,9 @@ class PresentationContext {
|
||||
currentUserProvider.overrideWith((ref) => CurrentUserProvider(service.user.service)),
|
||||
assetServiceProvider.overrideWithValue(service.asset.service),
|
||||
partnerServiceProvider.overrideWithValue(service.partner.service),
|
||||
gCastServiceProvider.overrideWithValue(service.cast),
|
||||
serverInfoServiceProvider.overrideWithValue(service.serverInfo),
|
||||
inLockedViewProvider.overrideWithValue(false),
|
||||
];
|
||||
|
||||
static Future<PresentationContext> create() async {
|
||||
@@ -61,9 +68,7 @@ class PresentationContext {
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
addTearDown(() {
|
||||
service.resetAll();
|
||||
});
|
||||
service.resetAll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
79
mobile/test/unit/services/asset_service_test.dart
Normal file
79
mobile/test/unit/services/asset_service_test.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/services/asset.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../../infrastructure/repository.mock.dart';
|
||||
import '../../repository.mocks.dart';
|
||||
import '../mocks.dart';
|
||||
|
||||
void main() {
|
||||
late AssetService sut;
|
||||
late RepositoryMocks mocks;
|
||||
late MockAssetApiRepository apiRepository;
|
||||
late MockRemoteAssetRepository remoteRepository;
|
||||
late MockRemoteExifRepository exifRepository;
|
||||
|
||||
setUp(() {
|
||||
mocks = RepositoryMocks();
|
||||
apiRepository = mocks.assetApi.api;
|
||||
remoteRepository = mocks.remoteAsset.repo;
|
||||
exifRepository = mocks.remoteExif.repo;
|
||||
|
||||
sut = AssetService(
|
||||
remoteRepository: remoteRepository,
|
||||
exifRepository: exifRepository,
|
||||
localRepository: MockDriftLocalAssetRepository(),
|
||||
apiRepository: apiRepository,
|
||||
);
|
||||
});
|
||||
|
||||
group('AssetService.updateDateTime', () {
|
||||
const ids = ['asset_id_1'];
|
||||
|
||||
test('sends the picked value to the api with its offset intact', () async {
|
||||
const picked = '2026-06-10T19:15:00.000+06:00';
|
||||
await sut.update(ids, dateTime: const .some(picked));
|
||||
|
||||
verify(() => apiRepository.update(ids, dateTimeOriginal: const .some(picked))).called(1);
|
||||
verify(() => remoteRepository.update(ids, createdAt: .some(DateTime.parse(picked)))).called(1);
|
||||
verify(
|
||||
() => exifRepository.update(
|
||||
ids,
|
||||
dateTimeOriginal: .some(DateTime.parse(picked)),
|
||||
timeZone: const .some('UTC+06:00'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('handles negative offsets', () async {
|
||||
const picked = '2026-01-05T08:00:00.000-05:30';
|
||||
await sut.update(ids, dateTime: const .some(picked));
|
||||
|
||||
verify(() => remoteRepository.update(ids, createdAt: .some(DateTime.parse(picked)))).called(1);
|
||||
verify(
|
||||
() => exifRepository.update(
|
||||
ids,
|
||||
dateTimeOriginal: .some(DateTime.parse(picked)),
|
||||
timeZone: const .some('UTC-05:30'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('writes no timezone when the value has no offset', () async {
|
||||
const picked = '2026-06-10T13:15:00.000Z';
|
||||
await sut.update(ids, dateTime: const .some(picked));
|
||||
|
||||
verify(() => remoteRepository.update(ids, createdAt: .some(DateTime.parse(picked)))).called(1);
|
||||
verify(
|
||||
() => exifRepository.update(ids, dateTimeOriginal: .some(DateTime.parse(picked)), timeZone: const .none()),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('is a no-op when there are no asset ids', () async {
|
||||
await sut.update(const [], dateTime: const .some('2026-06-10T19:15:00.000+06:00'));
|
||||
|
||||
verifyZeroInteractions(apiRepository);
|
||||
verifyZeroInteractions(remoteRepository);
|
||||
});
|
||||
});
|
||||
}
|
||||
48
mobile/test/unit/services/remote_album_service_test.dart
Normal file
48
mobile/test/unit/services/remote_album_service_test.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/services/remote_album.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../../service.mocks.dart';
|
||||
import '../mocks.dart';
|
||||
|
||||
void main() {
|
||||
late RemoteAlbumService sut;
|
||||
final mocks = RepositoryMocks();
|
||||
|
||||
setUpAll(() {
|
||||
registerFallbackValue(<String>[]);
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
sut = RemoteAlbumService(mocks.remoteAlbum, mocks.albumApi, MockForegroundUploadService());
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
mocks.resetAll();
|
||||
});
|
||||
|
||||
group('RemoteAlbumService', () {
|
||||
group('removeAssets', () {
|
||||
test('persists only the assets the server actually removed, not the whole request', () async {
|
||||
const albumId = 'album-1';
|
||||
const requested = ['asset-1', 'asset-2', 'asset-3'];
|
||||
const removed = ['asset-1', 'asset-3'];
|
||||
|
||||
// The server rejected 'asset-2'
|
||||
when(
|
||||
() => mocks.albumApi.removeAssets(albumId, requested),
|
||||
).thenAnswer((_) async => (removed: removed, failed: ['asset-2']));
|
||||
when(() => mocks.remoteAlbum.removeAssets(albumId, any())).thenAnswer((_) async {});
|
||||
|
||||
final count = await sut.removeAssets(albumId: albumId, assetIds: requested);
|
||||
|
||||
final persisted =
|
||||
verify(() => mocks.remoteAlbum.removeAssets(albumId, captureAny())).captured.single as List<String>;
|
||||
expect(persisted, removed);
|
||||
expect(persisted, isNot(contains('asset-2')));
|
||||
|
||||
expect(count, removed.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -52,7 +52,7 @@ void main() {
|
||||
final offlinePhoto = LocalAssetFactory.create();
|
||||
final remotePhoto = RemoteAssetFactory.create();
|
||||
|
||||
final AssetFilter<LocalAsset> syncedPhotos = AssetFilter(<BaseAsset>[
|
||||
final AssetFilter<BaseAsset> syncedPhotos = AssetFilter(<BaseAsset>[
|
||||
syncedPhoto,
|
||||
offlinePhoto,
|
||||
remotePhoto,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`
|
||||
INSERT INTO "user_metadata" ("userId", "key", "value")
|
||||
SELECT "user"."id", 'preferences', jsonb_build_object('people', jsonb_build_object('minimumFaces', "config"."minFaces"))
|
||||
FROM "user"
|
||||
CROSS JOIN (
|
||||
SELECT "value"->'machineLearning'->'facialRecognition'->'minFaces' AS "minFaces"
|
||||
FROM "system_metadata"
|
||||
WHERE "key" = 'system-config'
|
||||
AND "value"->'machineLearning'->'facialRecognition'->'minFaces' IS NOT NULL
|
||||
) AS "config"
|
||||
ON CONFLICT ("userId", "key") DO UPDATE
|
||||
SET "value" = "user_metadata"."value" || jsonb_build_object(
|
||||
'people',
|
||||
COALESCE("user_metadata"."value"->'people', '{}'::jsonb) || (EXCLUDED."value"->'people')
|
||||
)
|
||||
WHERE "user_metadata"."value"->'people'->'minimumFaces' IS NULL
|
||||
`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(): Promise<void> {
|
||||
// not supported
|
||||
}
|
||||
@@ -63,6 +63,7 @@
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import type { AssetResponseDto, SharedLinkResponseDto } from '@immich/sdk';
|
||||
import { untrack, type Snippet } from 'svelte';
|
||||
import { languageManager } from '$lib/managers/language-manager.svelte';
|
||||
|
||||
type Props = {
|
||||
asset: AssetResponseDto;
|
||||
@@ -232,7 +233,7 @@
|
||||
style:width={rasterWidth}
|
||||
style:height={rasterHeight}
|
||||
style:transform="scale({rasterScale})"
|
||||
style:transform-origin="0 0"
|
||||
style:transform-origin={languageManager.rtl ? 'right top' : 'left top'}
|
||||
style:will-change={maxRasterPixels > 0 ? 'transform' : undefined}
|
||||
>
|
||||
{#if show.alphaBackground}
|
||||
|
||||
Reference in New Issue
Block a user