mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Merge branch 'main' into feat/custom-date-range
This commit is contained in:
@@ -8,6 +8,7 @@ enum AlbumUserRole {
|
||||
// do not change this order!
|
||||
editor,
|
||||
viewer,
|
||||
owner,
|
||||
}
|
||||
|
||||
// Model for an album stored in the server
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
|
||||
part 'local_asset.model.dart';
|
||||
part 'remote_asset.model.dart';
|
||||
|
||||
@@ -11,6 +13,10 @@ enum AssetType {
|
||||
|
||||
enum AssetState { local, remote, merged }
|
||||
|
||||
// do not change!
|
||||
// keep in sync with PlatformAssetPlaybackStyle
|
||||
enum AssetPlaybackStyle { unknown, image, video, imageAnimated, livePhoto, videoLooping }
|
||||
|
||||
sealed class BaseAsset {
|
||||
final String name;
|
||||
final String? checksum;
|
||||
@@ -19,7 +25,7 @@ sealed class BaseAsset {
|
||||
final DateTime updatedAt;
|
||||
final int? width;
|
||||
final int? height;
|
||||
final int? durationInSeconds;
|
||||
final int? durationMs;
|
||||
final bool isFavorite;
|
||||
final String? livePhotoVideoId;
|
||||
final bool isEdited;
|
||||
@@ -32,7 +38,7 @@ sealed class BaseAsset {
|
||||
required this.updatedAt,
|
||||
this.width,
|
||||
this.height,
|
||||
this.durationInSeconds,
|
||||
this.durationMs,
|
||||
this.isFavorite = false,
|
||||
this.livePhotoVideoId,
|
||||
required this.isEdited,
|
||||
@@ -42,11 +48,22 @@ sealed class BaseAsset {
|
||||
bool get isVideo => type == AssetType.video;
|
||||
|
||||
bool get isMotionPhoto => livePhotoVideoId != null;
|
||||
bool get isAnimatedImage => playbackStyle == AssetPlaybackStyle.imageAnimated;
|
||||
|
||||
AssetPlaybackStyle get playbackStyle {
|
||||
if (isVideo) return AssetPlaybackStyle.video;
|
||||
if (isMotionPhoto) return AssetPlaybackStyle.livePhoto;
|
||||
if (isImage && durationMs != null && durationMs! > 0) {
|
||||
return AssetPlaybackStyle.imageAnimated;
|
||||
}
|
||||
if (isImage) return AssetPlaybackStyle.image;
|
||||
return AssetPlaybackStyle.unknown;
|
||||
}
|
||||
|
||||
Duration get duration {
|
||||
final durationInSeconds = this.durationInSeconds;
|
||||
if (durationInSeconds != null) {
|
||||
return Duration(seconds: durationInSeconds);
|
||||
final durationMs = this.durationMs;
|
||||
if (durationMs != null) {
|
||||
return Duration(milliseconds: durationMs);
|
||||
}
|
||||
return const Duration();
|
||||
}
|
||||
@@ -56,6 +73,8 @@ sealed class BaseAsset {
|
||||
bool get isLocalOnly => storage == AssetState.local;
|
||||
bool get isRemoteOnly => storage == AssetState.remote;
|
||||
|
||||
bool get isEditable => false;
|
||||
|
||||
// Overridden in subclasses
|
||||
AssetState get storage;
|
||||
String? get localId;
|
||||
@@ -71,7 +90,7 @@ sealed class BaseAsset {
|
||||
updatedAt: $updatedAt,
|
||||
width: ${width ?? "<NA>"},
|
||||
height: ${height ?? "<NA>"},
|
||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||
durationMs: ${durationMs ?? "<NA>"},
|
||||
isFavorite: $isFavorite,
|
||||
isEdited: $isEdited,
|
||||
}''';
|
||||
@@ -87,7 +106,7 @@ sealed class BaseAsset {
|
||||
updatedAt == other.updatedAt &&
|
||||
width == other.width &&
|
||||
height == other.height &&
|
||||
durationInSeconds == other.durationInSeconds &&
|
||||
durationMs == other.durationMs &&
|
||||
isFavorite == other.isFavorite &&
|
||||
isEdited == other.isEdited;
|
||||
}
|
||||
@@ -102,7 +121,7 @@ sealed class BaseAsset {
|
||||
updatedAt.hashCode ^
|
||||
width.hashCode ^
|
||||
height.hashCode ^
|
||||
durationInSeconds.hashCode ^
|
||||
durationMs.hashCode ^
|
||||
isFavorite.hashCode ^
|
||||
isEdited.hashCode;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ class LocalAsset extends BaseAsset {
|
||||
final String? remoteAssetId;
|
||||
final String? cloudId;
|
||||
final int orientation;
|
||||
@override
|
||||
final AssetPlaybackStyle playbackStyle;
|
||||
|
||||
final DateTime? adjustmentTime;
|
||||
final double? latitude;
|
||||
@@ -21,10 +23,11 @@ class LocalAsset extends BaseAsset {
|
||||
required super.updatedAt,
|
||||
super.width,
|
||||
super.height,
|
||||
super.durationInSeconds,
|
||||
super.durationMs,
|
||||
super.isFavorite = false,
|
||||
super.livePhotoVideoId,
|
||||
this.orientation = 0,
|
||||
required this.playbackStyle,
|
||||
this.adjustmentTime,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
@@ -55,7 +58,8 @@ class LocalAsset extends BaseAsset {
|
||||
updatedAt: $updatedAt,
|
||||
width: ${width ?? "<NA>"},
|
||||
height: ${height ?? "<NA>"},
|
||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||
durationMs: ${durationMs ?? "<NA>"},
|
||||
playbackStyle: $playbackStyle,
|
||||
remoteId: ${remoteId ?? "<NA>"},
|
||||
cloudId: ${cloudId ?? "<NA>"},
|
||||
checksum: ${checksum ?? "<NA>"},
|
||||
@@ -76,6 +80,7 @@ class LocalAsset extends BaseAsset {
|
||||
id == other.id &&
|
||||
cloudId == other.cloudId &&
|
||||
orientation == other.orientation &&
|
||||
playbackStyle == other.playbackStyle &&
|
||||
adjustmentTime == other.adjustmentTime &&
|
||||
latitude == other.latitude &&
|
||||
longitude == other.longitude;
|
||||
@@ -87,6 +92,7 @@ class LocalAsset extends BaseAsset {
|
||||
id.hashCode ^
|
||||
remoteId.hashCode ^
|
||||
orientation.hashCode ^
|
||||
playbackStyle.hashCode ^
|
||||
adjustmentTime.hashCode ^
|
||||
latitude.hashCode ^
|
||||
longitude.hashCode;
|
||||
@@ -102,9 +108,10 @@ class LocalAsset extends BaseAsset {
|
||||
DateTime? updatedAt,
|
||||
int? width,
|
||||
int? height,
|
||||
int? durationInSeconds,
|
||||
int? durationMs,
|
||||
bool? isFavorite,
|
||||
int? orientation,
|
||||
AssetPlaybackStyle? playbackStyle,
|
||||
DateTime? adjustmentTime,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
@@ -121,9 +128,10 @@ class LocalAsset extends BaseAsset {
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
width: width ?? this.width,
|
||||
height: height ?? this.height,
|
||||
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
||||
durationMs: durationMs ?? this.durationMs,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
orientation: orientation ?? this.orientation,
|
||||
playbackStyle: playbackStyle ?? this.playbackStyle,
|
||||
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
||||
latitude: latitude ?? this.latitude,
|
||||
longitude: longitude ?? this.longitude,
|
||||
|
||||
@@ -22,7 +22,7 @@ class RemoteAsset extends BaseAsset {
|
||||
required super.updatedAt,
|
||||
super.width,
|
||||
super.height,
|
||||
super.durationInSeconds,
|
||||
super.durationMs,
|
||||
super.isFavorite = false,
|
||||
this.thumbHash,
|
||||
this.visibility = AssetVisibility.timeline,
|
||||
@@ -43,6 +43,9 @@ class RemoteAsset extends BaseAsset {
|
||||
@override
|
||||
String get heroTag => '${localId ?? checksum}_$id';
|
||||
|
||||
@override
|
||||
bool get isEditable => isImage && !isMotionPhoto && !isAnimatedImage;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''Asset {
|
||||
@@ -54,7 +57,7 @@ class RemoteAsset extends BaseAsset {
|
||||
updatedAt: $updatedAt,
|
||||
width: ${width ?? "<NA>"},
|
||||
height: ${height ?? "<NA>"},
|
||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||
durationMs: ${durationMs ?? "<NA>"},
|
||||
localId: ${localId ?? "<NA>"},
|
||||
isFavorite: $isFavorite,
|
||||
thumbHash: ${thumbHash ?? "<NA>"},
|
||||
@@ -99,7 +102,7 @@ class RemoteAsset extends BaseAsset {
|
||||
DateTime? updatedAt,
|
||||
int? width,
|
||||
int? height,
|
||||
int? durationInSeconds,
|
||||
int? durationMs,
|
||||
bool? isFavorite,
|
||||
String? thumbHash,
|
||||
AssetVisibility? visibility,
|
||||
@@ -118,7 +121,7 @@ class RemoteAsset extends BaseAsset {
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
width: width ?? this.width,
|
||||
height: height ?? this.height,
|
||||
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
||||
durationMs: durationMs ?? this.durationMs,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
thumbHash: thumbHash ?? this.thumbHash,
|
||||
visibility: visibility ?? this.visibility,
|
||||
@@ -128,3 +131,81 @@ class RemoteAsset extends BaseAsset {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteAssetExif extends RemoteAsset {
|
||||
final ExifInfo exifInfo;
|
||||
|
||||
const RemoteAssetExif({
|
||||
required super.id,
|
||||
super.localId,
|
||||
required super.name,
|
||||
required super.ownerId,
|
||||
required super.checksum,
|
||||
required super.type,
|
||||
required super.createdAt,
|
||||
required super.updatedAt,
|
||||
super.width,
|
||||
super.height,
|
||||
super.durationMs,
|
||||
super.isFavorite = false,
|
||||
super.thumbHash,
|
||||
super.visibility = AssetVisibility.timeline,
|
||||
super.livePhotoVideoId,
|
||||
super.stackId,
|
||||
super.isEdited = false,
|
||||
this.exifInfo = const ExifInfo(),
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other is! RemoteAssetExif) return false;
|
||||
if (identical(this, other)) return true;
|
||||
return super == other && exifInfo == other.exifInfo;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => super.hashCode ^ exifInfo.hashCode;
|
||||
|
||||
@override
|
||||
RemoteAssetExif copyWith({
|
||||
String? id,
|
||||
String? localId,
|
||||
String? name,
|
||||
String? ownerId,
|
||||
String? checksum,
|
||||
AssetType? type,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
int? width,
|
||||
int? height,
|
||||
int? durationMs,
|
||||
bool? isFavorite,
|
||||
String? thumbHash,
|
||||
AssetVisibility? visibility,
|
||||
String? livePhotoVideoId,
|
||||
String? stackId,
|
||||
bool? isEdited,
|
||||
ExifInfo? exifInfo,
|
||||
}) {
|
||||
return RemoteAssetExif(
|
||||
id: id ?? this.id,
|
||||
localId: localId ?? this.localId,
|
||||
name: name ?? this.name,
|
||||
ownerId: ownerId ?? this.ownerId,
|
||||
checksum: checksum ?? this.checksum,
|
||||
type: type ?? this.type,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
width: width ?? this.width,
|
||||
height: height ?? this.height,
|
||||
durationMs: durationMs ?? this.durationMs,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
thumbHash: thumbHash ?? this.thumbHash,
|
||||
visibility: visibility ?? this.visibility,
|
||||
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
|
||||
stackId: stackId ?? this.stackId,
|
||||
isEdited: isEdited ?? this.isEdited,
|
||||
exifInfo: exifInfo ?? this.exifInfo, // Use the new parameter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import "package:openapi/api.dart" show CropParameters, RotateParameters, MirrorParameters;
|
||||
|
||||
enum AssetEditAction { rotate, crop, mirror, other }
|
||||
|
||||
sealed class AssetEdit {
|
||||
const AssetEdit();
|
||||
}
|
||||
|
||||
class CropEdit extends AssetEdit {
|
||||
final CropParameters parameters;
|
||||
|
||||
const CropEdit(this.parameters);
|
||||
}
|
||||
|
||||
class RotateEdit extends AssetEdit {
|
||||
final RotateParameters parameters;
|
||||
|
||||
const RotateEdit(this.parameters);
|
||||
}
|
||||
|
||||
class MirrorEdit extends AssetEdit {
|
||||
final MirrorParameters parameters;
|
||||
|
||||
const MirrorEdit(this.parameters);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:immich_mobile/domain/models/config/theme_config.dart';
|
||||
|
||||
class AppConfig {
|
||||
final ThemeConfig theme;
|
||||
|
||||
const AppConfig({this.theme = const ThemeConfig()});
|
||||
|
||||
AppConfig copyWith({ThemeConfig? theme}) => .new(theme: theme ?? this.theme);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || (other is AppConfig && other.theme == theme);
|
||||
|
||||
@override
|
||||
int get hashCode => theme.hashCode;
|
||||
|
||||
@override
|
||||
String toString() => 'AppConfig(theme: $theme)';
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||
|
||||
class SystemConfig {
|
||||
final LogLevel logLevel;
|
||||
|
||||
const SystemConfig({this.logLevel = .info});
|
||||
|
||||
SystemConfig copyWith({LogLevel? logLevel}) => SystemConfig(logLevel: logLevel ?? this.logLevel);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || (other is SystemConfig && other.logLevel == logLevel);
|
||||
|
||||
@override
|
||||
int get hashCode => logLevel.hashCode;
|
||||
|
||||
@override
|
||||
String toString() => 'SystemConfig(logLevel: $logLevel)';
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/constants/colors.dart';
|
||||
|
||||
class ThemeConfig {
|
||||
final ThemeMode mode;
|
||||
final ImmichColorPreset primaryColor;
|
||||
final bool dynamicTheme;
|
||||
final bool colorfulInterface;
|
||||
|
||||
const ThemeConfig({
|
||||
this.mode = .system,
|
||||
this.primaryColor = .indigo,
|
||||
this.dynamicTheme = false,
|
||||
this.colorfulInterface = true,
|
||||
});
|
||||
|
||||
ThemeConfig copyWith({
|
||||
ThemeMode? mode,
|
||||
ImmichColorPreset? primaryColor,
|
||||
bool? dynamicTheme,
|
||||
bool? colorfulInterface,
|
||||
}) => .new(
|
||||
mode: mode ?? this.mode,
|
||||
primaryColor: primaryColor ?? this.primaryColor,
|
||||
dynamicTheme: dynamicTheme ?? this.dynamicTheme,
|
||||
colorfulInterface: colorfulInterface ?? this.colorfulInterface,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is ThemeConfig &&
|
||||
other.mode == mode &&
|
||||
other.primaryColor == primaryColor &&
|
||||
other.dynamicTheme == dynamicTheme &&
|
||||
other.colorfulInterface == colorfulInterface);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(mode, primaryColor, dynamicTheme, colorfulInterface);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'ThemeConfig(mode: $mode, primaryColor: $primaryColor, dynamicTheme: $dynamicTheme, colorfulInterface: $colorfulInterface)';
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class DeviceAsset {
|
||||
final String assetId;
|
||||
final Uint8List hash;
|
||||
final DateTime modifiedTime;
|
||||
|
||||
const DeviceAsset({required this.assetId, required this.hash, required this.modifiedTime});
|
||||
|
||||
@override
|
||||
bool operator ==(covariant DeviceAsset other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other.assetId == assetId && other.hash == hash && other.modifiedTime == modifiedTime;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return assetId.hashCode ^ hash.hashCode ^ modifiedTime.hashCode;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DeviceAsset(assetId: $assetId, hash: $hash, modifiedTime: $modifiedTime)';
|
||||
}
|
||||
|
||||
DeviceAsset copyWith({String? assetId, Uint8List? hash, DateTime? modifiedTime}) {
|
||||
return DeviceAsset(
|
||||
assetId: assetId ?? this.assetId,
|
||||
hash: hash ?? this.hash,
|
||||
modifiedTime: modifiedTime ?? this.modifiedTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ class ExifInfo {
|
||||
final String? timeZone;
|
||||
final DateTime? dateTimeOriginal;
|
||||
final int? rating;
|
||||
final int? width;
|
||||
final int? height;
|
||||
|
||||
// GPS
|
||||
final double? latitude;
|
||||
@@ -27,7 +29,7 @@ class ExifInfo {
|
||||
bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0;
|
||||
|
||||
String get exposureTime {
|
||||
if (exposureSeconds == null) {
|
||||
if (exposureSeconds == null || exposureSeconds! <= 0 || exposureSeconds!.isNaN) {
|
||||
return "";
|
||||
}
|
||||
if (exposureSeconds! < 1) {
|
||||
@@ -48,6 +50,8 @@ class ExifInfo {
|
||||
this.timeZone,
|
||||
this.dateTimeOriginal,
|
||||
this.rating,
|
||||
this.width,
|
||||
this.height,
|
||||
this.isFlipped = false,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
@@ -74,6 +78,8 @@ class ExifInfo {
|
||||
other.timeZone == timeZone &&
|
||||
other.dateTimeOriginal == dateTimeOriginal &&
|
||||
other.rating == rating &&
|
||||
other.width == width &&
|
||||
other.height == height &&
|
||||
other.latitude == latitude &&
|
||||
other.longitude == longitude &&
|
||||
other.city == city &&
|
||||
@@ -98,6 +104,8 @@ class ExifInfo {
|
||||
timeZone.hashCode ^
|
||||
dateTimeOriginal.hashCode ^
|
||||
rating.hashCode ^
|
||||
width.hashCode ^
|
||||
height.hashCode ^
|
||||
latitude.hashCode ^
|
||||
longitude.hashCode ^
|
||||
city.hashCode ^
|
||||
@@ -123,6 +131,8 @@ 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'},
|
||||
@@ -146,6 +156,8 @@ exposureSeconds: ${exposureSeconds ?? 'NA'},
|
||||
String? timeZone,
|
||||
DateTime? dateTimeOriginal,
|
||||
int? rating,
|
||||
int? width,
|
||||
int? height,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
String? city,
|
||||
@@ -168,6 +180,8 @@ exposureSeconds: ${exposureSeconds ?? 'NA'},
|
||||
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,
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/constants/colors.dart';
|
||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||
import 'package:immich_mobile/domain/models/config/system_config.dart';
|
||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||
|
||||
enum MetadataDomain<T extends Object> {
|
||||
appConfig<AppConfig>('config.app'),
|
||||
systemConfig<SystemConfig>('config.system');
|
||||
|
||||
final String prefix;
|
||||
const MetadataDomain(this.prefix);
|
||||
}
|
||||
|
||||
enum MetadataKey<T extends Object> {
|
||||
// Theme
|
||||
primaryColor<ImmichColorPreset>(.appConfig, 'theme.primaryColor', .indigo, _EnumCodec(ImmichColorPreset.values)),
|
||||
themeMode<ThemeMode>(.appConfig, 'theme.mode', .system, _EnumCodec(ThemeMode.values)),
|
||||
dynamicTheme<bool>(.appConfig, 'theme.dynamicTheme', false),
|
||||
colorfulInterface<bool>(.appConfig, 'theme.colorfulInterface', true),
|
||||
|
||||
// Log
|
||||
logLevel<LogLevel>(.systemConfig, 'log.level', .info, _EnumCodec(LogLevel.values));
|
||||
|
||||
final MetadataDomain domain;
|
||||
final String name;
|
||||
final T defaultValue;
|
||||
final _MetadataCodec<T>? _codecOverride;
|
||||
|
||||
const MetadataKey(this.domain, this.name, this.defaultValue, [this._codecOverride]);
|
||||
|
||||
String get key => '${domain.prefix}.$name';
|
||||
|
||||
_MetadataCodec<T> get _codec => _codecOverride ?? _MetadataCodec.forPrimitive(defaultValue);
|
||||
|
||||
String encode(T value) => _codec.encode(value);
|
||||
|
||||
T decode(String raw) => _codec.decode(raw) ?? defaultValue;
|
||||
|
||||
static Map<String, MetadataKey<Object>> asKeyMap() => {for (var value in MetadataKey.values) value.key: value};
|
||||
}
|
||||
|
||||
sealed class _MetadataCodec<T extends Object> {
|
||||
const _MetadataCodec();
|
||||
|
||||
String encode(T value);
|
||||
T? decode(String raw);
|
||||
|
||||
static const Map<Type, _MetadataCodec<Object>> _primitives = {
|
||||
int: _PrimitiveCodec.integer,
|
||||
double: _PrimitiveCodec.real,
|
||||
bool: _PrimitiveCodec.boolean,
|
||||
String: _PrimitiveCodec.string,
|
||||
DateTime: _DateTimeCodec(),
|
||||
};
|
||||
|
||||
static _MetadataCodec<T> forPrimitive<T extends Object>(T sample) {
|
||||
final codec = _primitives[sample.runtimeType];
|
||||
if (codec == null) {
|
||||
throw StateError(
|
||||
'No primitive codec for ${sample.runtimeType}. Provide an explicit codec when defining the MetadataKey.',
|
||||
);
|
||||
}
|
||||
return codec as _MetadataCodec<T>;
|
||||
}
|
||||
}
|
||||
|
||||
final class _EnumCodec<T extends Enum> extends _MetadataCodec<T> {
|
||||
final List<T> values;
|
||||
|
||||
const _EnumCodec(this.values);
|
||||
|
||||
@override
|
||||
String encode(T value) => value.name;
|
||||
|
||||
@override
|
||||
T? decode(String raw) => values.firstWhereOrNull((v) => v.name == raw);
|
||||
}
|
||||
|
||||
final class _DateTimeCodec extends _MetadataCodec<DateTime> {
|
||||
const _DateTimeCodec();
|
||||
|
||||
@override
|
||||
String encode(DateTime value) => value.toIso8601String();
|
||||
|
||||
@override
|
||||
DateTime? decode(String raw) => DateTime.tryParse(raw);
|
||||
}
|
||||
|
||||
final class _PrimitiveCodec<T extends Object> extends _MetadataCodec<T> {
|
||||
final T? Function(String) _parse;
|
||||
|
||||
const _PrimitiveCodec._(this._parse);
|
||||
|
||||
@override
|
||||
String encode(T value) => value.toString();
|
||||
|
||||
@override
|
||||
T? decode(String raw) => _parse(raw);
|
||||
|
||||
static const integer = _PrimitiveCodec<int>._(int.tryParse);
|
||||
static const real = _PrimitiveCodec<double>._(double.tryParse);
|
||||
static const boolean = _PrimitiveCodec<bool>._(bool.tryParse);
|
||||
static const string = _PrimitiveCodec<String>._(_identity);
|
||||
|
||||
static String? _identity(String s) => s;
|
||||
}
|
||||
@@ -3,30 +3,21 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
|
||||
class SearchResult {
|
||||
final List<BaseAsset> assets;
|
||||
final double scrollOffset;
|
||||
final int? nextPage;
|
||||
|
||||
const SearchResult({required this.assets, this.scrollOffset = 0.0, this.nextPage});
|
||||
|
||||
SearchResult copyWith({List<BaseAsset>? assets, int? nextPage, double? scrollOffset}) {
|
||||
return SearchResult(
|
||||
assets: assets ?? this.assets,
|
||||
nextPage: nextPage ?? this.nextPage,
|
||||
scrollOffset: scrollOffset ?? this.scrollOffset,
|
||||
);
|
||||
}
|
||||
const SearchResult({required this.assets, this.nextPage});
|
||||
|
||||
@override
|
||||
String toString() => 'SearchResult(assets: ${assets.length}, nextPage: $nextPage, scrollOffset: $scrollOffset)';
|
||||
String toString() => 'SearchResult(assets: ${assets.length}, nextPage: $nextPage)';
|
||||
|
||||
@override
|
||||
bool operator ==(covariant SearchResult other) {
|
||||
if (identical(this, other)) return true;
|
||||
final listEquals = const DeepCollectionEquality().equals;
|
||||
|
||||
return listEquals(other.assets, assets) && other.nextPage == nextPage && other.scrollOffset == scrollOffset;
|
||||
return listEquals(other.assets, assets) && other.nextPage == nextPage;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => assets.hashCode ^ nextPage.hashCode ^ scrollOffset.hashCode;
|
||||
int get hashCode => assets.hashCode ^ nextPage.hashCode;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ enum StoreKey<T> {
|
||||
// user settings from [AppSettingsEnum] below:
|
||||
loadPreview<bool>._(100),
|
||||
loadOriginal<bool>._(101),
|
||||
themeMode<String>._(102),
|
||||
tilesPerRow<int>._(103),
|
||||
dynamicLayout<bool>._(104),
|
||||
groupAssetsBy<int>._(105),
|
||||
@@ -35,7 +34,6 @@ enum StoreKey<T> {
|
||||
albumThumbnailCacheSize<int>._(112),
|
||||
selectedAlbumSortOrder<int>._(113),
|
||||
advancedTroubleshooting<bool>._(114),
|
||||
logLevel<int>._(115),
|
||||
preferRemoteImage<bool>._(116),
|
||||
loopVideo<bool>._(117),
|
||||
// map related settings
|
||||
@@ -50,11 +48,6 @@ enum StoreKey<T> {
|
||||
enableHapticFeedback<bool>._(126),
|
||||
customHeaders<String>._(127),
|
||||
|
||||
// theme settings
|
||||
primaryColor<String>._(128),
|
||||
dynamicTheme<bool>._(129),
|
||||
colorfulInterface<bool>._(130),
|
||||
|
||||
syncAlbums<bool>._(131),
|
||||
|
||||
// Auto endpoint switching
|
||||
@@ -98,7 +91,14 @@ enum StoreKey<T> {
|
||||
cleanupCutoffDaysAgo<int>._(1011),
|
||||
cleanupDefaultsInitialized<bool>._(1012),
|
||||
|
||||
syncMigrationStatus<String>._(1013);
|
||||
syncMigrationStatus<String>._(1013),
|
||||
|
||||
// Legacy keys that have been migrated to the new metadata store
|
||||
legacyPrimaryColor<String>._(128),
|
||||
legacyDynamicTheme<bool>._(129),
|
||||
legacyColorfulInterface<bool>._(130),
|
||||
legacyThemeMode<String>._(102),
|
||||
legacyLogLevel<int>._(115);
|
||||
|
||||
const StoreKey._(this.id);
|
||||
final int id;
|
||||
|
||||
Reference in New Issue
Block a user