From 18d4a796bb3728b6102a3cbe970f78b33b85ca94 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Wed, 5 Aug 2026 12:00:49 -0700 Subject: [PATCH] chore(mobile): simple freezed implementation on some models (#30168) * chore(mobile): example freezed implementation on some models * Don't commit Freezed generated code * Freezed easy pass * Formatting fix * Generate Flutter debugging info --- mobile/.gitignore | 1 + .../lib/domain/models/album/album.model.dart | 133 ++--------- .../models/album/local_album.model.dart | 95 ++------ .../lib/domain/models/asset_face.model.dart | 115 ++------- .../domain/models/config/album_config.dart | 32 +-- .../lib/domain/models/config/app_config.dart | 120 ++-------- .../domain/models/config/backup_config.dart | 62 ++--- .../models/config/feature_message_config.dart | 23 +- .../domain/models/config/image_config.dart | 23 +- .../domain/models/config/share_config.dart | 20 +- .../models/config/slideshow_config.dart | 45 +--- .../domain/models/config/theme_config.dart | 50 +--- .../domain/models/config/timeline_config.dart | 36 +-- .../domain/models/config/viewer_config.dart | 45 +--- mobile/lib/domain/models/exif.model.dart | 218 +++--------------- mobile/lib/domain/models/ocr.model.dart | 146 ++---------- mobile/lib/domain/models/person.model.dart | 113 ++------- mobile/lib/domain/models/stack.model.dart | 67 ++---- .../domain/models/user_metadata.model.dart | 68 ++---- .../lib/models/activities/activity.model.dart | 68 +----- mobile/lib/models/auth/auth_state.model.dart | 82 ++----- mobile/lib/models/map/map_marker.model.dart | 31 +-- mobile/lib/models/map/map_state.model.dart | 89 ++----- .../server_info/server_disk_info.model.dart | 65 ++---- .../server_info/server_features.model.dart | 90 ++------ .../models/server_info/server_info.model.dart | 76 ++---- .../models/shared_link/shared_link.model.dart | 146 +++--------- .../pages/edit/editor.provider.dart | 118 ++-------- .../widgets/timeline/timeline.state.dart | 24 +- .../asset_viewer/asset_viewer.provider.dart | 82 ++----- .../backup/drift_backup.provider.dart | 81 ++----- .../lib/providers/server_info.provider.dart | 2 +- .../lib/providers/sync_status.provider.dart | 82 +++---- mobile/lib/providers/websocket.provider.dart | 29 +-- mobile/pubspec.lock | 16 ++ mobile/pubspec.yaml | 2 + 36 files changed, 512 insertions(+), 1983 deletions(-) diff --git a/mobile/.gitignore b/mobile/.gitignore index 6730e3c46a..c426d46ec3 100644 --- a/mobile/.gitignore +++ b/mobile/.gitignore @@ -32,6 +32,7 @@ .pub/ /build/ lib/**/*.drift.dart +lib/**/*.freezed.dart lib/routing/router.gr.dart test/drift/main/generated/ diff --git a/mobile/lib/domain/models/album/album.model.dart b/mobile/lib/domain/models/album/album.model.dart index 63f4ed6be3..e63a3774e1 100644 --- a/mobile/lib/domain/models/album/album.model.dart +++ b/mobile/lib/domain/models/album/album.model.dart @@ -1,3 +1,8 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'album.model.freezed.dart'; + enum AlbumAssetOrder { // do not change this order! asc, @@ -12,118 +17,20 @@ enum AlbumUserRole { } // Model for an album stored in the server -class RemoteAlbum { - final String id; - final String name; - final String ownerId; - final String description; - final DateTime createdAt; - final DateTime updatedAt; - final String? thumbnailAssetId; - final bool isActivityEnabled; - final AlbumAssetOrder order; - final int assetCount; - final String ownerName; - final bool isShared; - - const RemoteAlbum({ - required this.id, - required this.name, - required this.ownerId, - required this.description, - required this.createdAt, - required this.updatedAt, - this.thumbnailAssetId, - required this.isActivityEnabled, - required this.order, - required this.assetCount, - required this.ownerName, - required this.isShared, - }); - - @override - String toString() { - return '''Album { - id: $id, - name: $name, - ownerId: $ownerId, - description: $description, - createdAt: $createdAt, - updatedAt: $updatedAt, - isActivityEnabled: $isActivityEnabled, - order: $order, - thumbnailAssetId: ${thumbnailAssetId ?? ""} - assetCount: $assetCount - ownerName: $ownerName - isShared: $isShared - }'''; - } - - @override - bool operator ==(Object other) { - if (other is! RemoteAlbum) { - return false; - } - if (identical(this, other)) { - return true; - } - return id == other.id && - name == other.name && - ownerId == other.ownerId && - description == other.description && - createdAt == other.createdAt && - updatedAt == other.updatedAt && - thumbnailAssetId == other.thumbnailAssetId && - isActivityEnabled == other.isActivityEnabled && - order == other.order && - assetCount == other.assetCount && - ownerName == other.ownerName && - isShared == other.isShared; - } - - @override - int get hashCode { - return id.hashCode ^ - name.hashCode ^ - ownerId.hashCode ^ - description.hashCode ^ - createdAt.hashCode ^ - updatedAt.hashCode ^ - thumbnailAssetId.hashCode ^ - isActivityEnabled.hashCode ^ - order.hashCode ^ - assetCount.hashCode ^ - ownerName.hashCode ^ - isShared.hashCode; - } - - RemoteAlbum copyWith({ - String? id, - String? name, - String? ownerId, - String? description, - DateTime? createdAt, - DateTime? updatedAt, +@freezed +abstract class RemoteAlbum with _$RemoteAlbum { + const factory RemoteAlbum({ + required String id, + required String name, + required String ownerId, + required String description, + required DateTime createdAt, + required DateTime updatedAt, String? thumbnailAssetId, - bool? isActivityEnabled, - AlbumAssetOrder? order, - int? assetCount, - String? ownerName, - bool? isShared, - }) { - return RemoteAlbum( - id: id ?? this.id, - name: name ?? this.name, - ownerId: ownerId ?? this.ownerId, - description: description ?? this.description, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, - isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, - order: order ?? this.order, - assetCount: assetCount ?? this.assetCount, - ownerName: ownerName ?? this.ownerName, - isShared: isShared ?? this.isShared, - ); - } + required bool isActivityEnabled, + required AlbumAssetOrder order, + required int assetCount, + required String ownerName, + required bool isShared, + }) = _RemoteAlbum; } diff --git a/mobile/lib/domain/models/album/local_album.model.dart b/mobile/lib/domain/models/album/local_album.model.dart index 9e8521fa02..cddc1d1953 100644 --- a/mobile/lib/domain/models/album/local_album.model.dart +++ b/mobile/lib/domain/models/album/local_album.model.dart @@ -1,3 +1,8 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'local_album.model.freezed.dart'; + enum BackupSelection { // Used to sort albums based on the backupSelection // selected -> none -> excluded @@ -7,85 +12,15 @@ enum BackupSelection { excluded, } -class LocalAlbum { - final String id; - final String name; - final DateTime updatedAt; - final bool isIosSharedAlbum; - - final int assetCount; - final BackupSelection backupSelection; - final String? linkedRemoteAlbumId; - - const LocalAlbum({ - required this.id, - required this.name, - required this.updatedAt, - this.assetCount = 0, - this.backupSelection = BackupSelection.none, - this.isIosSharedAlbum = false, - this.linkedRemoteAlbumId, - }); - - LocalAlbum copyWith({ - String? id, - String? name, - DateTime? updatedAt, - int? assetCount, - BackupSelection? backupSelection, - bool? isIosSharedAlbum, +@freezed +abstract class LocalAlbum with _$LocalAlbum { + const factory LocalAlbum({ + required String id, + required String name, + required DateTime updatedAt, + @Default(0) int assetCount, + @Default(BackupSelection.none) BackupSelection backupSelection, + @Default(false) bool isIosSharedAlbum, String? linkedRemoteAlbumId, - }) { - return LocalAlbum( - id: id ?? this.id, - name: name ?? this.name, - updatedAt: updatedAt ?? this.updatedAt, - assetCount: assetCount ?? this.assetCount, - backupSelection: backupSelection ?? this.backupSelection, - isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, - linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, - ); - } - - @override - bool operator ==(Object other) { - if (other is! LocalAlbum) { - return false; - } - if (identical(this, other)) { - return true; - } - - return other.id == id && - other.name == name && - other.updatedAt == updatedAt && - other.assetCount == assetCount && - other.backupSelection == backupSelection && - other.isIosSharedAlbum == isIosSharedAlbum && - other.linkedRemoteAlbumId == linkedRemoteAlbumId; - } - - @override - int get hashCode { - return id.hashCode ^ - name.hashCode ^ - updatedAt.hashCode ^ - assetCount.hashCode ^ - backupSelection.hashCode ^ - isIosSharedAlbum.hashCode ^ - linkedRemoteAlbumId.hashCode; - } - - @override - String toString() { - return '''LocalAlbum: { -id: $id, -name: $name, -updatedAt: $updatedAt, -assetCount: $assetCount, -backupSelection: $backupSelection, -isIosSharedAlbum: $isIosSharedAlbum -linkedRemoteAlbumId: $linkedRemoteAlbumId, -}'''; - } + }) = _LocalAlbum; } diff --git a/mobile/lib/domain/models/asset_face.model.dart b/mobile/lib/domain/models/asset_face.model.dart index 1388836946..c65f159fe7 100644 --- a/mobile/lib/domain/models/asset_face.model.dart +++ b/mobile/lib/domain/models/asset_face.model.dart @@ -1,100 +1,21 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'asset_face.model.freezed.dart'; + // Model for an asset face stored in the server -class AssetFace { - final String id; - final String assetId; - final String? personId; - final int imageWidth; - final int imageHeight; - final int boundingBoxX1; - final int boundingBoxY1; - final int boundingBoxX2; - final int boundingBoxY2; - final String sourceType; - - const AssetFace({ - required this.id, - required this.assetId, - this.personId, - required this.imageWidth, - required this.imageHeight, - required this.boundingBoxX1, - required this.boundingBoxY1, - required this.boundingBoxX2, - required this.boundingBoxY2, - required this.sourceType, - }); - - AssetFace copyWith({ - String? id, - String? assetId, +@freezed +abstract class AssetFace with _$AssetFace { + const factory AssetFace({ + required String id, + required String assetId, String? personId, - int? imageWidth, - int? imageHeight, - int? boundingBoxX1, - int? boundingBoxY1, - int? boundingBoxX2, - int? boundingBoxY2, - String? sourceType, - }) { - return AssetFace( - id: id ?? this.id, - assetId: assetId ?? this.assetId, - personId: personId ?? this.personId, - imageWidth: imageWidth ?? this.imageWidth, - imageHeight: imageHeight ?? this.imageHeight, - boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, - boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, - boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, - boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, - sourceType: sourceType ?? this.sourceType, - ); - } - - @override - String toString() { - return '''AssetFace { - id: $id, - assetId: $assetId, - personId: ${personId ?? ""}, - imageWidth: $imageWidth, - imageHeight: $imageHeight, - boundingBoxX1: $boundingBoxX1, - boundingBoxY1: $boundingBoxY1, - boundingBoxX2: $boundingBoxX2, - boundingBoxY2: $boundingBoxY2, - sourceType: $sourceType, -}'''; - } - - @override - bool operator ==(covariant AssetFace other) { - if (identical(this, other)) { - return true; - } - - return other.id == id && - other.assetId == assetId && - other.personId == personId && - other.imageWidth == imageWidth && - other.imageHeight == imageHeight && - other.boundingBoxX1 == boundingBoxX1 && - other.boundingBoxY1 == boundingBoxY1 && - other.boundingBoxX2 == boundingBoxX2 && - other.boundingBoxY2 == boundingBoxY2 && - other.sourceType == sourceType; - } - - @override - int get hashCode { - return id.hashCode ^ - assetId.hashCode ^ - personId.hashCode ^ - imageWidth.hashCode ^ - imageHeight.hashCode ^ - boundingBoxX1.hashCode ^ - boundingBoxY1.hashCode ^ - boundingBoxX2.hashCode ^ - boundingBoxY2.hashCode ^ - sourceType.hashCode; - } + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) = _AssetFace; } diff --git a/mobile/lib/domain/models/config/album_config.dart b/mobile/lib/domain/models/config/album_config.dart index a83fc32fbf..16deca1b8f 100644 --- a/mobile/lib/domain/models/config/album_config.dart +++ b/mobile/lib/domain/models/config/album_config.dart @@ -1,26 +1,14 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; -class AlbumConfig { - final AlbumSortMode sortMode; - final bool isReverse; - final bool isGrid; +part 'album_config.freezed.dart'; - const AlbumConfig({this.sortMode = AlbumSortMode.mostRecent, this.isReverse = true, this.isGrid = false}); - - AlbumConfig copyWith({AlbumSortMode? sortMode, bool? isReverse, bool? isGrid}) => AlbumConfig( - sortMode: sortMode ?? this.sortMode, - isReverse: isReverse ?? this.isReverse, - isGrid: isGrid ?? this.isGrid, - ); - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is AlbumConfig && other.sortMode == sortMode && other.isReverse == isReverse && other.isGrid == isGrid); - - @override - int get hashCode => Object.hash(sortMode, isReverse, isGrid); - - @override - String toString() => 'AlbumConfig(sortMode: $sortMode, isReverse: $isReverse, isGrid: $isGrid)'; +@freezed +abstract class AlbumConfig with _$AlbumConfig { + const factory AlbumConfig({ + @Default(AlbumSortMode.mostRecent) AlbumSortMode sortMode, + @Default(true) bool isReverse, + @Default(false) bool isGrid, + }) = _AlbumConfig; } diff --git a/mobile/lib/domain/models/config/app_config.dart b/mobile/lib/domain/models/config/app_config.dart index df147f3e3a..bc089e621e 100644 --- a/mobile/lib/domain/models/config/app_config.dart +++ b/mobile/lib/domain/models/config/app_config.dart @@ -1,4 +1,6 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/config/album_config.dart'; @@ -19,107 +21,29 @@ import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; import 'package:immich_mobile/utils/semver.dart'; +part 'app_config.freezed.dart'; + const defaultConfig = AppConfig(); -class AppConfig { - final LogLevel logLevel; - final ThemeConfig theme; - final CleanupConfig cleanup; - final MapConfig map; - final TimelineConfig timeline; - final ImageConfig image; - final ViewerConfig viewer; - final SlideshowConfig slideshow; - final AlbumConfig album; - final BackupConfig backup; - final NetworkConfig network; - final ShareConfig share; - final FeatureMessageConfig featureMessage; +@freezed +abstract class AppConfig with _$AppConfig { + const AppConfig._(); - const AppConfig({ - this.logLevel = .info, - this.theme = const .new(), - this.cleanup = const .new(), - this.map = const .new(), - this.timeline = const .new(), - this.image = const .new(), - this.viewer = const .new(), - this.slideshow = const .new(), - this.album = const .new(), - this.backup = const .new(), - this.network = const .new(), - this.share = const .new(), - this.featureMessage = const .new(), - }); - - AppConfig copyWith({ - LogLevel? logLevel, - ThemeConfig? theme, - CleanupConfig? cleanup, - MapConfig? map, - TimelineConfig? timeline, - ImageConfig? image, - ViewerConfig? viewer, - SlideshowConfig? slideshow, - AlbumConfig? album, - BackupConfig? backup, - NetworkConfig? network, - ShareConfig? share, - FeatureMessageConfig? featureMessage, - }) => .new( - logLevel: logLevel ?? this.logLevel, - theme: theme ?? this.theme, - cleanup: cleanup ?? this.cleanup, - map: map ?? this.map, - timeline: timeline ?? this.timeline, - image: image ?? this.image, - viewer: viewer ?? this.viewer, - slideshow: slideshow ?? this.slideshow, - album: album ?? this.album, - backup: backup ?? this.backup, - network: network ?? this.network, - share: share ?? this.share, - featureMessage: featureMessage ?? this.featureMessage, - ); - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is AppConfig && - other.logLevel == logLevel && - other.theme == theme && - other.cleanup == cleanup && - other.map == map && - other.timeline == timeline && - other.image == image && - other.viewer == viewer && - other.slideshow == slideshow && - other.album == album && - other.backup == backup && - other.network == network && - other.share == share && - other.featureMessage == featureMessage); - - @override - int get hashCode => Object.hash( - logLevel, - theme, - cleanup, - map, - timeline, - image, - viewer, - slideshow, - album, - backup, - network, - share, - featureMessage, - ); - - @override - String toString() => - 'AppConfig(logLevel: $logLevel, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network, share: $share, featureMessage: $featureMessage)'; + const factory AppConfig({ + @Default(LogLevel.info) LogLevel logLevel, + @Default(ThemeConfig()) ThemeConfig theme, + @Default(CleanupConfig()) CleanupConfig cleanup, + @Default(MapConfig()) MapConfig map, + @Default(TimelineConfig()) TimelineConfig timeline, + @Default(ImageConfig()) ImageConfig image, + @Default(ViewerConfig()) ViewerConfig viewer, + @Default(SlideshowConfig()) SlideshowConfig slideshow, + @Default(AlbumConfig()) AlbumConfig album, + @Default(BackupConfig()) BackupConfig backup, + @Default(NetworkConfig()) NetworkConfig network, + @Default(ShareConfig()) ShareConfig share, + @Default(FeatureMessageConfig()) FeatureMessageConfig featureMessage, + }) = _AppConfig; T read(SettingsKey key) => (switch (key) { diff --git a/mobile/lib/domain/models/config/backup_config.dart b/mobile/lib/domain/models/config/backup_config.dart index 19f91a4ed7..4177408fcc 100644 --- a/mobile/lib/domain/models/config/backup_config.dart +++ b/mobile/lib/domain/models/config/backup_config.dart @@ -1,52 +1,16 @@ -class BackupConfig { - final bool enabled; - final bool useCellularForVideos; - final bool useCellularForPhotos; - final bool requireCharging; - final int triggerDelay; - final bool syncAlbums; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; - const BackupConfig({ - this.enabled = false, - this.useCellularForVideos = false, - this.useCellularForPhotos = false, - this.requireCharging = false, - this.triggerDelay = 30, - this.syncAlbums = false, - }); +part 'backup_config.freezed.dart'; - BackupConfig copyWith({ - bool? enabled, - bool? useCellularForVideos, - bool? useCellularForPhotos, - bool? requireCharging, - int? triggerDelay, - bool? syncAlbums, - }) => BackupConfig( - enabled: enabled ?? this.enabled, - useCellularForVideos: useCellularForVideos ?? this.useCellularForVideos, - useCellularForPhotos: useCellularForPhotos ?? this.useCellularForPhotos, - requireCharging: requireCharging ?? this.requireCharging, - triggerDelay: triggerDelay ?? this.triggerDelay, - syncAlbums: syncAlbums ?? this.syncAlbums, - ); - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is BackupConfig && - other.enabled == enabled && - other.useCellularForVideos == useCellularForVideos && - other.useCellularForPhotos == useCellularForPhotos && - other.requireCharging == requireCharging && - other.triggerDelay == triggerDelay && - other.syncAlbums == syncAlbums); - - @override - int get hashCode => - Object.hash(enabled, useCellularForVideos, useCellularForPhotos, requireCharging, triggerDelay, syncAlbums); - - @override - String toString() => - 'BackupConfig(enabled: $enabled, useCellularForVideos: $useCellularForVideos, useCellularForPhotos: $useCellularForPhotos, requireCharging: $requireCharging, triggerDelay: $triggerDelay, syncAlbums: $syncAlbums)'; +@freezed +abstract class BackupConfig with _$BackupConfig { + const factory BackupConfig({ + @Default(false) bool enabled, + @Default(false) bool useCellularForVideos, + @Default(false) bool useCellularForPhotos, + @Default(false) bool requireCharging, + @Default(30) int triggerDelay, + @Default(false) bool syncAlbums, + }) = _BackupConfig; } diff --git a/mobile/lib/domain/models/config/feature_message_config.dart b/mobile/lib/domain/models/config/feature_message_config.dart index d5c7c69550..0fc13fd51f 100644 --- a/mobile/lib/domain/models/config/feature_message_config.dart +++ b/mobile/lib/domain/models/config/feature_message_config.dart @@ -1,20 +1,11 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/utils/semver.dart'; -class FeatureMessageConfig { - final SemVer seenRelease; +part 'feature_message_config.freezed.dart'; - const FeatureMessageConfig({this.seenRelease = const SemVer(major: 0, minor: 0, patch: 0)}); - - FeatureMessageConfig copyWith({SemVer? seenRelease}) => - FeatureMessageConfig(seenRelease: seenRelease ?? this.seenRelease); - - @override - bool operator ==(Object other) => - identical(this, other) || (other is FeatureMessageConfig && other.seenRelease == seenRelease); - - @override - int get hashCode => seenRelease.hashCode; - - @override - String toString() => 'FeatureMessageConfig(seenRelease: $seenRelease)'; +@freezed +abstract class FeatureMessageConfig with _$FeatureMessageConfig { + const factory FeatureMessageConfig({@Default(SemVer(major: 0, minor: 0, patch: 0)) SemVer seenRelease}) = + _FeatureMessageConfig; } diff --git a/mobile/lib/domain/models/config/image_config.dart b/mobile/lib/domain/models/config/image_config.dart index 8410a9010b..a51193d1d3 100644 --- a/mobile/lib/domain/models/config/image_config.dart +++ b/mobile/lib/domain/models/config/image_config.dart @@ -1,20 +1,9 @@ -class ImageConfig { - final bool preferRemote; - final bool loadOriginal; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; - const ImageConfig({this.preferRemote = false, this.loadOriginal = false}); +part 'image_config.freezed.dart'; - ImageConfig copyWith({bool? preferRemote, bool? loadOriginal}) => - ImageConfig(preferRemote: preferRemote ?? this.preferRemote, loadOriginal: loadOriginal ?? this.loadOriginal); - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is ImageConfig && other.preferRemote == preferRemote && other.loadOriginal == loadOriginal); - - @override - int get hashCode => Object.hash(preferRemote, loadOriginal); - - @override - String toString() => 'ImageConfig(preferRemoteImage: $preferRemote, loadOriginal: $loadOriginal)'; +@freezed +abstract class ImageConfig with _$ImageConfig { + const factory ImageConfig({@Default(false) bool preferRemote, @Default(false) bool loadOriginal}) = _ImageConfig; } diff --git a/mobile/lib/domain/models/config/share_config.dart b/mobile/lib/domain/models/config/share_config.dart index 898867ff78..49530aca30 100644 --- a/mobile/lib/domain/models/config/share_config.dart +++ b/mobile/lib/domain/models/config/share_config.dart @@ -1,18 +1,10 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/constants/enums.dart'; -class ShareConfig { - final ShareAssetType fileType; +part 'share_config.freezed.dart'; - const ShareConfig({this.fileType = ShareAssetType.original}); - - ShareConfig copyWith({ShareAssetType? fileType}) => ShareConfig(fileType: fileType ?? this.fileType); - - @override - bool operator ==(Object other) => identical(this, other) || (other is ShareConfig && other.fileType == fileType); - - @override - int get hashCode => fileType.hashCode; - - @override - String toString() => 'ShareConfig(fileType: $fileType)'; +@freezed +abstract class ShareConfig with _$ShareConfig { + const factory ShareConfig({@Default(ShareAssetType.original) ShareAssetType fileType}) = _ShareConfig; } diff --git a/mobile/lib/domain/models/config/slideshow_config.dart b/mobile/lib/domain/models/config/slideshow_config.dart index 6bcdaadc77..cacba1a89e 100644 --- a/mobile/lib/domain/models/config/slideshow_config.dart +++ b/mobile/lib/domain/models/config/slideshow_config.dart @@ -1,38 +1,15 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/constants/enums.dart'; -class SlideshowConfig { - final bool repeat; - final int duration; - final SlideshowLook look; - final SlideshowDirection direction; +part 'slideshow_config.freezed.dart'; - const SlideshowConfig({ - this.repeat = true, - this.duration = 5, - this.look = SlideshowLook.blurredBackground, - this.direction = SlideshowDirection.forward, - }); - - SlideshowConfig copyWith({bool? repeat, int? duration, SlideshowLook? look, SlideshowDirection? direction}) => - SlideshowConfig( - repeat: repeat ?? this.repeat, - duration: duration ?? this.duration, - look: look ?? this.look, - direction: direction ?? this.direction, - ); - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is SlideshowConfig && - other.repeat == repeat && - other.duration == duration && - other.look == look && - other.direction == direction); - - @override - int get hashCode => Object.hash(repeat, duration, look, direction); - - @override - String toString() => 'SlideshowConfig(repeat: $repeat, duration: $duration, look: $look, direction: $direction)'; +@freezed +abstract class SlideshowConfig with _$SlideshowConfig { + const factory SlideshowConfig({ + @Default(true) bool repeat, + @Default(5) int duration, + @Default(SlideshowLook.blurredBackground) SlideshowLook look, + @Default(SlideshowDirection.forward) SlideshowDirection direction, + }) = _SlideshowConfig; } diff --git a/mobile/lib/domain/models/config/theme_config.dart b/mobile/lib/domain/models/config/theme_config.dart index fa955c5d46..ac651a8062 100644 --- a/mobile/lib/domain/models/config/theme_config.dart +++ b/mobile/lib/domain/models/config/theme_config.dart @@ -1,44 +1,16 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/constants/colors.dart'; -class ThemeConfig { - final ThemeMode mode; - final ImmichColorPreset primaryColor; - final bool dynamicTheme; - final bool colorfulInterface; +part 'theme_config.freezed.dart'; - 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)'; +@freezed +abstract class ThemeConfig with _$ThemeConfig { + const factory ThemeConfig({ + @Default(ThemeMode.system) ThemeMode mode, + @Default(ImmichColorPreset.indigo) ImmichColorPreset primaryColor, + @Default(false) bool dynamicTheme, + @Default(true) bool colorfulInterface, + }) = _ThemeConfig; } diff --git a/mobile/lib/domain/models/config/timeline_config.dart b/mobile/lib/domain/models/config/timeline_config.dart index 4b6b9d5625..c4681b0a62 100644 --- a/mobile/lib/domain/models/config/timeline_config.dart +++ b/mobile/lib/domain/models/config/timeline_config.dart @@ -1,30 +1,14 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; -class TimelineConfig { - final int tilesPerRow; - final GroupAssetsBy groupAssetsBy; - final bool storageIndicator; +part 'timeline_config.freezed.dart'; - const TimelineConfig({this.tilesPerRow = 4, this.groupAssetsBy = GroupAssetsBy.day, this.storageIndicator = true}); - - TimelineConfig copyWith({int? tilesPerRow, GroupAssetsBy? groupAssetsBy, bool? storageIndicator}) => TimelineConfig( - tilesPerRow: tilesPerRow ?? this.tilesPerRow, - groupAssetsBy: groupAssetsBy ?? this.groupAssetsBy, - storageIndicator: storageIndicator ?? this.storageIndicator, - ); - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is TimelineConfig && - other.tilesPerRow == tilesPerRow && - other.groupAssetsBy == groupAssetsBy && - other.storageIndicator == storageIndicator); - - @override - int get hashCode => Object.hash(tilesPerRow, groupAssetsBy, storageIndicator); - - @override - String toString() => - 'TimelineConfig(tilesPerRow: $tilesPerRow, groupAssetsBy: $groupAssetsBy, storageIndicator: $storageIndicator)'; +@freezed +abstract class TimelineConfig with _$TimelineConfig { + const factory TimelineConfig({ + @Default(4) int tilesPerRow, + @Default(GroupAssetsBy.day) GroupAssetsBy groupAssetsBy, + @Default(true) bool storageIndicator, + }) = _TimelineConfig; } diff --git a/mobile/lib/domain/models/config/viewer_config.dart b/mobile/lib/domain/models/config/viewer_config.dart index 595f2bee5d..e799843034 100644 --- a/mobile/lib/domain/models/config/viewer_config.dart +++ b/mobile/lib/domain/models/config/viewer_config.dart @@ -1,37 +1,14 @@ -class ViewerConfig { - final bool loopVideo; - final bool loadOriginalVideo; - final bool autoPlayVideo; - final bool tapToNavigate; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; - const ViewerConfig({ - this.loopVideo = true, - this.loadOriginalVideo = false, - this.autoPlayVideo = true, - this.tapToNavigate = false, - }); +part 'viewer_config.freezed.dart'; - ViewerConfig copyWith({bool? loopVideo, bool? loadOriginalVideo, bool? autoPlayVideo, bool? tapToNavigate}) => - ViewerConfig( - loopVideo: loopVideo ?? this.loopVideo, - loadOriginalVideo: loadOriginalVideo ?? this.loadOriginalVideo, - autoPlayVideo: autoPlayVideo ?? this.autoPlayVideo, - tapToNavigate: tapToNavigate ?? this.tapToNavigate, - ); - - @override - bool operator ==(Object other) => - identical(this, other) || - (other is ViewerConfig && - other.loopVideo == loopVideo && - other.loadOriginalVideo == loadOriginalVideo && - other.autoPlayVideo == autoPlayVideo && - other.tapToNavigate == tapToNavigate); - - @override - int get hashCode => Object.hash(loopVideo, loadOriginalVideo, autoPlayVideo, tapToNavigate); - - @override - String toString() => - 'ViewerConfig(loopVideo: $loopVideo, loadOriginalVideo: $loadOriginalVideo, autoPlayVideo: $autoPlayVideo, tapToNavigate: $tapToNavigate)'; +@freezed +abstract class ViewerConfig with _$ViewerConfig { + const factory ViewerConfig({ + @Default(true) bool loopVideo, + @Default(false) bool loadOriginalVideo, + @Default(true) bool autoPlayVideo, + @Default(false) bool tapToNavigate, + }) = _ViewerConfig; } diff --git a/mobile/lib/domain/models/exif.model.dart b/mobile/lib/domain/models/exif.model.dart index 4284aef2ab..3536048ecc 100644 --- a/mobile/lib/domain/models/exif.model.dart +++ b/mobile/lib/domain/models/exif.model.dart @@ -1,30 +1,40 @@ -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; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; - // GPS - final double? latitude; - final double? longitude; - final String? city; - final String? state; - final String? country; +part 'exif.model.freezed.dart'; - // Camera related - final String? make; - final String? model; - final String? lens; - final double? f; - final double? mm; - final int? iso; - final double? exposureSeconds; +@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; bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0; @@ -41,162 +51,4 @@ class 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 ?? ''}, -make: ${make ?? 'NA'}, -model: ${model ?? 'NA'}, -lens: ${lens ?? 'NA'}, -f: ${f ?? 'NA'}, -mm: ${mm ?? ''}, -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, - ); - } } diff --git a/mobile/lib/domain/models/ocr.model.dart b/mobile/lib/domain/models/ocr.model.dart index 403c06d44e..bfb398bc79 100644 --- a/mobile/lib/domain/models/ocr.model.dart +++ b/mobile/lib/domain/models/ocr.model.dart @@ -1,128 +1,24 @@ -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; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.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, - }); +part 'ocr.model.freezed.dart'; - 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; - } +@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; } diff --git a/mobile/lib/domain/models/person.model.dart b/mobile/lib/domain/models/person.model.dart index c7cdcff3af..38d740eeb4 100644 --- a/mobile/lib/domain/models/person.model.dart +++ b/mobile/lib/domain/models/person.model.dart @@ -1,5 +1,10 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'person.model.freezed.dart'; + // TODO: Remove PersonDto once Isar is removed class PersonDto { const PersonDto({ @@ -93,102 +98,18 @@ class PersonDto { } // Model for a person stored in the server -class DriftPerson { - final String id; - final DateTime createdAt; - final DateTime updatedAt; - final String ownerId; - final String name; - final String? faceAssetId; - final bool isFavorite; - final bool isHidden; - final String? color; - final DateTime? birthDate; - - const DriftPerson({ - required this.id, - required this.createdAt, - required this.updatedAt, - required this.ownerId, - required this.name, - this.faceAssetId, - required this.isFavorite, - required this.isHidden, - required this.color, - this.birthDate, - }); - - DriftPerson copyWith({ - String? id, - DateTime? createdAt, - DateTime? updatedAt, - String? ownerId, - String? name, +@freezed +abstract class DriftPerson with _$DriftPerson { + const factory DriftPerson({ + required String id, + required DateTime createdAt, + required DateTime updatedAt, + required String ownerId, + required String name, String? faceAssetId, - bool? isFavorite, - bool? isHidden, - String? color, + required bool isFavorite, + required bool isHidden, + required String? color, DateTime? birthDate, - }) { - return DriftPerson( - id: id ?? this.id, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - ownerId: ownerId ?? this.ownerId, - name: name ?? this.name, - faceAssetId: faceAssetId ?? this.faceAssetId, - isFavorite: isFavorite ?? this.isFavorite, - isHidden: isHidden ?? this.isHidden, - color: color ?? this.color, - birthDate: birthDate ?? this.birthDate, - ); - } - - @override - String toString() { - return '''Person { - id: $id, - createdAt: $createdAt, - updatedAt: $updatedAt, - ownerId: $ownerId, - name: $name, - faceAssetId: ${faceAssetId ?? ""}, - isFavorite: $isFavorite, - isHidden: $isHidden, - color: ${color ?? ""}, - birthDate: ${birthDate ?? ""} -}'''; - } - - @override - bool operator ==(covariant DriftPerson other) { - if (identical(this, other)) { - return true; - } - - return other.id == id && - other.createdAt == createdAt && - other.updatedAt == updatedAt && - other.ownerId == ownerId && - other.name == name && - other.faceAssetId == faceAssetId && - other.isFavorite == isFavorite && - other.isHidden == isHidden && - other.color == color && - other.birthDate == birthDate; - } - - @override - int get hashCode { - return id.hashCode ^ - createdAt.hashCode ^ - updatedAt.hashCode ^ - ownerId.hashCode ^ - name.hashCode ^ - faceAssetId.hashCode ^ - isFavorite.hashCode ^ - isHidden.hashCode ^ - color.hashCode ^ - birthDate.hashCode; - } + }) = _DriftPerson; } diff --git a/mobile/lib/domain/models/stack.model.dart b/mobile/lib/domain/models/stack.model.dart index f17f5788c9..4e88a02c6c 100644 --- a/mobile/lib/domain/models/stack.model.dart +++ b/mobile/lib/domain/models/stack.model.dart @@ -1,57 +1,18 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'stack.model.freezed.dart'; + // Model for a stack stored in the server -class Stack { - final String id; - final DateTime createdAt; - final DateTime updatedAt; - final String ownerId; - final String primaryAssetId; - - const Stack({ - required this.id, - required this.createdAt, - required this.updatedAt, - required this.ownerId, - required this.primaryAssetId, - }); - - Stack copyWith({String? id, DateTime? createdAt, DateTime? updatedAt, String? ownerId, String? primaryAssetId}) { - return Stack( - id: id ?? this.id, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - ownerId: ownerId ?? this.ownerId, - primaryAssetId: primaryAssetId ?? this.primaryAssetId, - ); - } - - @override - String toString() { - return '''Stack { - id: $id, - createdAt: $createdAt, - updatedAt: $updatedAt, - ownerId: $ownerId, - primaryAssetId: $primaryAssetId -}'''; - } - - @override - bool operator ==(covariant Stack other) { - if (identical(this, other)) { - return true; - } - - return other.id == id && - other.createdAt == createdAt && - other.updatedAt == updatedAt && - other.ownerId == ownerId && - other.primaryAssetId == primaryAssetId; - } - - @override - int get hashCode { - return id.hashCode ^ createdAt.hashCode ^ updatedAt.hashCode ^ ownerId.hashCode ^ primaryAssetId.hashCode; - } +@freezed +abstract class Stack with _$Stack { + const factory Stack({ + required String id, + required DateTime createdAt, + required DateTime updatedAt, + required String ownerId, + required String primaryAssetId, + }) = _Stack; } class StackResponse { diff --git a/mobile/lib/domain/models/user_metadata.model.dart b/mobile/lib/domain/models/user_metadata.model.dart index 0e702ba868..8bce1aca13 100644 --- a/mobile/lib/domain/models/user_metadata.model.dart +++ b/mobile/lib/domain/models/user_metadata.model.dart @@ -1,5 +1,9 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; +part 'user_metadata.model.freezed.dart'; + enum UserMetadataKey { // do not change this order! onboarding, @@ -224,61 +228,17 @@ licenseKey: $licenseKey, } // Model for a user metadata stored in the server -class UserMetadata { - final String userId; - final UserMetadataKey key; - final Onboarding? onboarding; - final Preferences? preferences; - final License? license; - - const UserMetadata({required this.userId, required this.key, this.onboarding, this.preferences, this.license}) - : assert( - onboarding != null || preferences != null || license != null, - 'One of onboarding, preferences and license must be provided', - ); - - UserMetadata copyWith({ - String? userId, - UserMetadataKey? key, +@freezed +abstract class UserMetadata with _$UserMetadata { + @Assert( + 'onboarding != null || preferences != null || license != null', + 'One of onboarding, preferences and license must be provided', + ) + const factory UserMetadata({ + required String userId, + required UserMetadataKey key, Onboarding? onboarding, Preferences? preferences, License? license, - }) { - return UserMetadata( - userId: userId ?? this.userId, - key: key ?? this.key, - onboarding: onboarding ?? this.onboarding, - preferences: preferences ?? this.preferences, - license: license ?? this.license, - ); - } - - @override - String toString() { - return '''UserMetadata: { -userId: $userId, -key: $key, -onboarding: ${onboarding ?? ""}, -preferences: ${preferences ?? ""}, -license: ${license ?? ""}, -}'''; - } - - @override - bool operator ==(covariant UserMetadata other) { - if (identical(this, other)) { - return true; - } - - return other.userId == userId && - other.key == key && - other.onboarding == onboarding && - other.preferences == preferences && - other.license == license; - } - - @override - int get hashCode { - return userId.hashCode ^ key.hashCode ^ onboarding.hashCode ^ preferences.hashCode ^ license.hashCode; - } + }) = _UserMetadata; } diff --git a/mobile/lib/models/activities/activity.model.dart b/mobile/lib/models/activities/activity.model.dart index d3f99aea64..9a589ef729 100644 --- a/mobile/lib/models/activities/activity.model.dart +++ b/mobile/lib/models/activities/activity.model.dart @@ -1,65 +1,21 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; +part 'activity.model.freezed.dart'; + enum ActivityType { comment, like } -class Activity { - final String id; - final String? assetId; - final String? comment; - final DateTime createdAt; - final ActivityType type; - final UserDto user; - - const Activity({ - required this.id, - this.assetId, - this.comment, - required this.createdAt, - required this.type, - required this.user, - }); - - Activity copyWith({ - String? id, +@freezed +abstract class Activity with _$Activity { + const factory Activity({ + required String id, String? assetId, String? comment, - DateTime? createdAt, - ActivityType? type, - UserDto? user, - }) { - return Activity( - id: id ?? this.id, - assetId: assetId ?? this.assetId, - comment: comment ?? this.comment, - createdAt: createdAt ?? this.createdAt, - type: type ?? this.type, - user: user ?? this.user, - ); - } - - @override - String toString() { - return 'Activity(id: $id, assetId: $assetId, comment: $comment, createdAt: $createdAt, type: $type, user: $user)'; - } - - @override - bool operator ==(covariant Activity other) { - if (identical(this, other)) { - return true; - } - - return other.id == id && - other.assetId == assetId && - other.comment == comment && - other.createdAt == createdAt && - other.type == type && - other.user == user; - } - - @override - int get hashCode { - return id.hashCode ^ assetId.hashCode ^ comment.hashCode ^ createdAt.hashCode ^ type.hashCode ^ user.hashCode; - } + required DateTime createdAt, + required ActivityType type, + required UserDto user, + }) = _Activity; } class ActivityStats { diff --git a/mobile/lib/models/auth/auth_state.model.dart b/mobile/lib/models/auth/auth_state.model.dart index c8a8018929..0a3bf983b2 100644 --- a/mobile/lib/models/auth/auth_state.model.dart +++ b/mobile/lib/models/auth/auth_state.model.dart @@ -1,71 +1,17 @@ -class AuthState { - final String deviceId; - final String userId; - final String userEmail; - final bool isAuthenticated; - final String name; - final bool isAdmin; - final String profileImagePath; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; - const AuthState({ - required this.deviceId, - required this.userId, - required this.userEmail, - required this.isAuthenticated, - required this.name, - required this.isAdmin, - required this.profileImagePath, - }); +part 'auth_state.model.freezed.dart'; - AuthState copyWith({ - String? deviceId, - String? userId, - String? userEmail, - bool? isAuthenticated, - String? name, - bool? isAdmin, - String? profileImagePath, - }) { - return AuthState( - deviceId: deviceId ?? this.deviceId, - userId: userId ?? this.userId, - userEmail: userEmail ?? this.userEmail, - isAuthenticated: isAuthenticated ?? this.isAuthenticated, - name: name ?? this.name, - isAdmin: isAdmin ?? this.isAdmin, - profileImagePath: profileImagePath ?? this.profileImagePath, - ); - } - - @override - String toString() { - return 'AuthenticationState(deviceId: $deviceId, userId: $userId, userEmail: $userEmail, isAuthenticated: $isAuthenticated, name: $name, isAdmin: $isAdmin, profileImagePath: $profileImagePath)'; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - - return other is AuthState && - other.deviceId == deviceId && - other.userId == userId && - other.userEmail == userEmail && - other.isAuthenticated == isAuthenticated && - other.name == name && - other.isAdmin == isAdmin && - other.profileImagePath == profileImagePath; - } - - @override - int get hashCode { - return deviceId.hashCode ^ - userId.hashCode ^ - userEmail.hashCode ^ - isAuthenticated.hashCode ^ - name.hashCode ^ - isAdmin.hashCode ^ - profileImagePath.hashCode; - } +@freezed +abstract class AuthState with _$AuthState { + const factory AuthState({ + required String deviceId, + required String userId, + required String userEmail, + required bool isAuthenticated, + required String name, + required bool isAdmin, + required String profileImagePath, + }) = _AuthState; } diff --git a/mobile/lib/models/map/map_marker.model.dart b/mobile/lib/models/map/map_marker.model.dart index d730f9bd6d..4093747e84 100644 --- a/mobile/lib/models/map/map_marker.model.dart +++ b/mobile/lib/models/map/map_marker.model.dart @@ -1,29 +1,14 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:openapi/api.dart'; -class MapMarker { - final LatLng latLng; - final String assetRemoteId; - const MapMarker({required this.latLng, required this.assetRemoteId}); +part 'map_marker.model.freezed.dart'; - MapMarker copyWith({LatLng? latLng, String? assetRemoteId}) { - return MapMarker(latLng: latLng ?? this.latLng, assetRemoteId: assetRemoteId ?? this.assetRemoteId); - } +@freezed +abstract class MapMarker with _$MapMarker { + const factory MapMarker({required LatLng latLng, required String assetRemoteId}) = _MapMarker; - MapMarker.fromDto(MapMarkerResponseDto dto) : latLng = LatLng(dto.lat, dto.lon), assetRemoteId = dto.id; - - @override - String toString() => 'MapMarker(latLng: $latLng, assetRemoteId: $assetRemoteId)'; - - @override - bool operator ==(covariant MapMarker other) { - if (identical(this, other)) { - return true; - } - - return other.latLng == latLng && other.assetRemoteId == assetRemoteId; - } - - @override - int get hashCode => latLng.hashCode ^ assetRemoteId.hashCode; + factory MapMarker.fromDto(MapMarkerResponseDto dto) => + MapMarker(latLng: LatLng(dto.lat, dto.lon), assetRemoteId: dto.id); } diff --git a/mobile/lib/models/map/map_state.model.dart b/mobile/lib/models/map/map_state.model.dart index a4863aa465..f7bd435073 100644 --- a/mobile/lib/models/map/map_state.model.dart +++ b/mobile/lib/models/map/map_state.model.dart @@ -1,79 +1,20 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -class MapState { - final ThemeMode themeMode; - final bool showFavoriteOnly; - final bool includeArchived; - final bool withPartners; - final int relativeTime; - final bool shouldRefetchMarkers; - final AsyncValue lightStyleFetched; - final AsyncValue darkStyleFetched; +part 'map_state.model.freezed.dart'; - const MapState({ - this.themeMode = ThemeMode.system, - this.showFavoriteOnly = false, - this.includeArchived = false, - this.withPartners = false, - this.relativeTime = 0, - this.shouldRefetchMarkers = false, - this.lightStyleFetched = const AsyncLoading(), - this.darkStyleFetched = const AsyncLoading(), - }); - - MapState copyWith({ - ThemeMode? themeMode, - bool? showFavoriteOnly, - bool? includeArchived, - bool? withPartners, - int? relativeTime, - bool? shouldRefetchMarkers, - AsyncValue? lightStyleFetched, - AsyncValue? darkStyleFetched, - }) { - return MapState( - themeMode: themeMode ?? this.themeMode, - showFavoriteOnly: showFavoriteOnly ?? this.showFavoriteOnly, - includeArchived: includeArchived ?? this.includeArchived, - withPartners: withPartners ?? this.withPartners, - relativeTime: relativeTime ?? this.relativeTime, - shouldRefetchMarkers: shouldRefetchMarkers ?? this.shouldRefetchMarkers, - lightStyleFetched: lightStyleFetched ?? this.lightStyleFetched, - darkStyleFetched: darkStyleFetched ?? this.darkStyleFetched, - ); - } - - @override - String toString() { - return 'MapState(themeMode: $themeMode, showFavoriteOnly: $showFavoriteOnly, includeArchived: $includeArchived, withPartners: $withPartners, relativeTime: $relativeTime, shouldRefetchMarkers: $shouldRefetchMarkers, lightStyleFetched: $lightStyleFetched, darkStyleFetched: $darkStyleFetched)'; - } - - @override - bool operator ==(covariant MapState other) { - if (identical(this, other)) { - return true; - } - - return other.themeMode == themeMode && - other.showFavoriteOnly == showFavoriteOnly && - other.includeArchived == includeArchived && - other.withPartners == withPartners && - other.relativeTime == relativeTime && - other.shouldRefetchMarkers == shouldRefetchMarkers && - other.lightStyleFetched == lightStyleFetched && - other.darkStyleFetched == darkStyleFetched; - } - - @override - int get hashCode { - return themeMode.hashCode ^ - showFavoriteOnly.hashCode ^ - includeArchived.hashCode ^ - withPartners.hashCode ^ - relativeTime.hashCode ^ - shouldRefetchMarkers.hashCode ^ - lightStyleFetched.hashCode ^ - darkStyleFetched.hashCode; - } +@freezed +abstract class MapState with _$MapState { + const factory MapState({ + @Default(ThemeMode.system) ThemeMode themeMode, + @Default(false) bool showFavoriteOnly, + @Default(false) bool includeArchived, + @Default(false) bool withPartners, + @Default(0) int relativeTime, + @Default(false) bool shouldRefetchMarkers, + @Default(AsyncLoading()) AsyncValue lightStyleFetched, + @Default(AsyncLoading()) AsyncValue darkStyleFetched, + }) = _MapState; } diff --git a/mobile/lib/models/server_info/server_disk_info.model.dart b/mobile/lib/models/server_info/server_disk_info.model.dart index 16e58b331d..910f06b065 100644 --- a/mobile/lib/models/server_info/server_disk_info.model.dart +++ b/mobile/lib/models/server_info/server_disk_info.model.dart @@ -1,53 +1,22 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:openapi/api.dart'; -class ServerDiskInfo { - final String diskAvailable; - final String diskSize; - final String diskUse; - final double diskUsagePercentage; +part 'server_disk_info.model.freezed.dart'; - const ServerDiskInfo({ - required this.diskAvailable, - required this.diskSize, - required this.diskUse, - required this.diskUsagePercentage, - }); +@freezed +abstract class ServerDiskInfo with _$ServerDiskInfo { + const factory ServerDiskInfo({ + required String diskAvailable, + required String diskSize, + required String diskUse, + required double diskUsagePercentage, + }) = _ServerDiskInfo; - ServerDiskInfo copyWith({String? diskAvailable, String? diskSize, String? diskUse, double? diskUsagePercentage}) { - return ServerDiskInfo( - diskAvailable: diskAvailable ?? this.diskAvailable, - diskSize: diskSize ?? this.diskSize, - diskUse: diskUse ?? this.diskUse, - diskUsagePercentage: diskUsagePercentage ?? this.diskUsagePercentage, - ); - } - - @override - String toString() { - return 'ServerDiskInfo(diskAvailable: $diskAvailable, diskSize: $diskSize, diskUse: $diskUse, diskUsagePercentage: $diskUsagePercentage)'; - } - - ServerDiskInfo.fromDto(ServerStorageResponseDto dto) - : diskAvailable = dto.diskAvailable, - diskSize = dto.diskSize, - diskUse = dto.diskUse, - diskUsagePercentage = dto.diskUsagePercentage; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - - return other is ServerDiskInfo && - other.diskAvailable == diskAvailable && - other.diskSize == diskSize && - other.diskUse == diskUse && - other.diskUsagePercentage == diskUsagePercentage; - } - - @override - int get hashCode { - return diskAvailable.hashCode ^ diskSize.hashCode ^ diskUse.hashCode ^ diskUsagePercentage.hashCode; - } + factory ServerDiskInfo.fromDto(ServerStorageResponseDto dto) => ServerDiskInfo( + diskAvailable: dto.diskAvailable, + diskSize: dto.diskSize, + diskUse: dto.diskUse, + diskUsagePercentage: dto.diskUsagePercentage, + ); } diff --git a/mobile/lib/models/server_info/server_features.model.dart b/mobile/lib/models/server_info/server_features.model.dart index c288c1bfbf..4de7e2e3cd 100644 --- a/mobile/lib/models/server_info/server_features.model.dart +++ b/mobile/lib/models/server_info/server_features.model.dart @@ -1,74 +1,26 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:openapi/api.dart'; -class ServerFeatures { - final bool trash; - final bool map; - final bool oauthEnabled; - final bool passwordLogin; - final bool ocr; - final bool smartSearch; +part 'server_features.model.freezed.dart'; - const ServerFeatures({ - required this.trash, - required this.map, - required this.oauthEnabled, - required this.passwordLogin, - this.ocr = false, - this.smartSearch = false, - }); +@freezed +abstract class ServerFeatures with _$ServerFeatures { + const factory ServerFeatures({ + required bool trash, + required bool map, + required bool oauthEnabled, + required bool passwordLogin, + @Default(false) bool ocr, + @Default(false) bool smartSearch, + }) = _ServerFeatures; - ServerFeatures copyWith({ - bool? trash, - bool? map, - bool? oauthEnabled, - bool? passwordLogin, - bool? ocr, - bool? smartSearch, - }) { - return ServerFeatures( - trash: trash ?? this.trash, - map: map ?? this.map, - oauthEnabled: oauthEnabled ?? this.oauthEnabled, - passwordLogin: passwordLogin ?? this.passwordLogin, - ocr: ocr ?? this.ocr, - smartSearch: smartSearch ?? this.smartSearch, - ); - } - - @override - String toString() { - return 'ServerFeatures(trash: $trash, map: $map, oauthEnabled: $oauthEnabled, passwordLogin: $passwordLogin, ocr: $ocr, smartSearch: $smartSearch)'; - } - - ServerFeatures.fromDto(ServerFeaturesDto dto) - : trash = dto.trash, - map = dto.map, - oauthEnabled = dto.oauth, - passwordLogin = dto.passwordLogin, - ocr = dto.ocr, - smartSearch = dto.smartSearch; - - @override - bool operator ==(covariant ServerFeatures other) { - if (identical(this, other)) { - return true; - } - - return other.trash == trash && - other.map == map && - other.oauthEnabled == oauthEnabled && - other.passwordLogin == passwordLogin && - other.ocr == ocr && - other.smartSearch == smartSearch; - } - - @override - int get hashCode { - return trash.hashCode ^ - map.hashCode ^ - oauthEnabled.hashCode ^ - passwordLogin.hashCode ^ - ocr.hashCode ^ - smartSearch.hashCode; - } + factory ServerFeatures.fromDto(ServerFeaturesDto dto) => ServerFeatures( + trash: dto.trash, + map: dto.map, + oauthEnabled: dto.oauth, + passwordLogin: dto.passwordLogin, + ocr: dto.ocr, + smartSearch: dto.smartSearch, + ); } diff --git a/mobile/lib/models/server_info/server_info.model.dart b/mobile/lib/models/server_info/server_info.model.dart index 33d6393e15..adc55d3f4e 100644 --- a/mobile/lib/models/server_info/server_info.model.dart +++ b/mobile/lib/models/server_info/server_info.model.dart @@ -1,9 +1,13 @@ import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/models/server_info/server_config.model.dart'; import 'package:immich_mobile/models/server_info/server_disk_info.model.dart'; import 'package:immich_mobile/models/server_info/server_features.model.dart'; import 'package:immich_mobile/models/server_info/server_version.model.dart'; +part 'server_info.model.freezed.dart'; + enum VersionStatus { upToDate, clientOutOfDate, @@ -18,68 +22,14 @@ enum VersionStatus { }; } -class ServerInfo { - final ServerVersion serverVersion; - final ServerVersion? latestVersion; - final ServerFeatures serverFeatures; - final ServerConfig serverConfig; - final ServerDiskInfo serverDiskInfo; - final VersionStatus versionStatus; - - const ServerInfo({ - required this.serverVersion, - this.latestVersion, - required this.serverFeatures, - required this.serverConfig, - required this.serverDiskInfo, - required this.versionStatus, - }); - - ServerInfo copyWith({ - ServerVersion? serverVersion, +@freezed +abstract class ServerInfo with _$ServerInfo { + const factory ServerInfo({ + required ServerVersion serverVersion, ServerVersion? latestVersion, - ServerFeatures? serverFeatures, - ServerConfig? serverConfig, - ServerDiskInfo? serverDiskInfo, - VersionStatus? versionStatus, - }) { - return ServerInfo( - serverVersion: serverVersion ?? this.serverVersion, - latestVersion: latestVersion ?? this.latestVersion, - serverFeatures: serverFeatures ?? this.serverFeatures, - serverConfig: serverConfig ?? this.serverConfig, - serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo, - versionStatus: versionStatus ?? this.versionStatus, - ); - } - - @override - String toString() { - return 'ServerInfo(serverVersion: $serverVersion, latestVersion: $latestVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, serverDiskInfo: $serverDiskInfo, versionStatus: $versionStatus)'; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - - return other is ServerInfo && - other.serverVersion == serverVersion && - other.latestVersion == latestVersion && - other.serverFeatures == serverFeatures && - other.serverConfig == serverConfig && - other.serverDiskInfo == serverDiskInfo && - other.versionStatus == versionStatus; - } - - @override - int get hashCode { - return serverVersion.hashCode ^ - latestVersion.hashCode ^ - serverFeatures.hashCode ^ - serverConfig.hashCode ^ - serverDiskInfo.hashCode ^ - versionStatus.hashCode; - } + required ServerFeatures serverFeatures, + required ServerConfig serverConfig, + required ServerDiskInfo serverDiskInfo, + required VersionStatus versionStatus, + }) = _ServerInfo; } diff --git a/mobile/lib/models/shared_link/shared_link.model.dart b/mobile/lib/models/shared_link/shared_link.model.dart index e7b65a96ef..cf96609d3c 100644 --- a/mobile/lib/models/shared_link/shared_link.model.dart +++ b/mobile/lib/models/shared_link/shared_link.model.dart @@ -1,119 +1,47 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:openapi/api.dart'; +part 'shared_link.model.freezed.dart'; + enum SharedLinkSource { album, individual } -class SharedLink { - final String id; - final String title; - final bool allowDownload; - final bool allowUpload; - final String? thumbAssetId; - final String? description; - final String? password; - final DateTime? expiresAt; - final String key; - final bool showMetadata; - final SharedLinkSource type; - final String? slug; +@freezed +abstract class SharedLink with _$SharedLink { + const factory SharedLink({ + required String id, + required String title, + required bool allowDownload, + required bool allowUpload, + required String? thumbAssetId, + required String? description, + required String? password, + required DateTime? expiresAt, + required String key, + required bool showMetadata, + required SharedLinkSource type, + required String? slug, + }) = _SharedLink; - const SharedLink({ - required this.id, - required this.title, - required this.allowDownload, - required this.allowUpload, - required this.thumbAssetId, - required this.description, - required this.password, - required this.expiresAt, - required this.key, - required this.showMetadata, - required this.type, - required this.slug, - }); - - SharedLink copyWith({ - String? id, - String? title, - String? thumbAssetId, - bool? allowDownload, - bool? allowUpload, - String? description, - String? password, - DateTime? expiresAt, - String? key, - bool? showMetadata, - SharedLinkSource? type, - String? slug, - }) { + factory SharedLink.fromDto(SharedLinkResponseDto dto) { + final isAlbum = dto.type == SharedLinkType.ALBUM; return SharedLink( - id: id ?? this.id, - title: title ?? this.title, - thumbAssetId: thumbAssetId ?? this.thumbAssetId, - allowDownload: allowDownload ?? this.allowDownload, - allowUpload: allowUpload ?? this.allowUpload, - description: description ?? this.description, - password: password ?? this.password, - expiresAt: expiresAt ?? this.expiresAt, - key: key ?? this.key, - showMetadata: showMetadata ?? this.showMetadata, - type: type ?? this.type, - slug: slug ?? this.slug, - ); - } - - SharedLink.fromDto(SharedLinkResponseDto dto) - : id = dto.id, - allowDownload = dto.allowDownload, - allowUpload = dto.allowUpload, - description = dto.description, - password = dto.password, - expiresAt = dto.expiresAt, - key = dto.key, - showMetadata = dto.showMetadata, - slug = dto.slug, - type = dto.type == SharedLinkType.ALBUM ? SharedLinkSource.album : SharedLinkSource.individual, - title = dto.type == SharedLinkType.ALBUM - ? dto.album.orElse(null)?.albumName.toUpperCase() ?? "UNKNOWN SHARE" - : "INDIVIDUAL SHARE", - thumbAssetId = dto.type == SharedLinkType.ALBUM + id: dto.id, + allowDownload: dto.allowDownload, + allowUpload: dto.allowUpload, + description: dto.description, + password: dto.password, + expiresAt: dto.expiresAt, + key: dto.key, + showMetadata: dto.showMetadata, + slug: dto.slug, + type: isAlbum ? SharedLinkSource.album : SharedLinkSource.individual, + title: isAlbum ? dto.album.orElse(null)?.albumName.toUpperCase() ?? "UNKNOWN SHARE" : "INDIVIDUAL SHARE", + thumbAssetId: isAlbum ? dto.album.orElse(null)?.albumThumbnailAssetId : dto.assets.isNotEmpty ? dto.assets[0].id - : null; - - @override - String toString() => - 'SharedLink(id=$id, title=$title, thumbAssetId=$thumbAssetId, allowDownload=$allowDownload, allowUpload=$allowUpload, description=$description, password=$password, expiresAt=$expiresAt, key=$key, showMetadata=$showMetadata, type=$type, slug=$slug)'; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is SharedLink && - other.id == id && - other.title == title && - other.thumbAssetId == thumbAssetId && - other.allowDownload == allowDownload && - other.allowUpload == allowUpload && - other.description == description && - other.password == password && - other.expiresAt == expiresAt && - other.key == key && - other.showMetadata == showMetadata && - other.type == type && - other.slug == slug; - - @override - int get hashCode => - id.hashCode ^ - title.hashCode ^ - thumbAssetId.hashCode ^ - allowDownload.hashCode ^ - allowUpload.hashCode ^ - description.hashCode ^ - password.hashCode ^ - expiresAt.hashCode ^ - key.hashCode ^ - showMetadata.hashCode ^ - type.hashCode ^ - slug.hashCode; + : null, + ); + } } diff --git a/mobile/lib/presentation/pages/edit/editor.provider.dart b/mobile/lib/presentation/pages/edit/editor.provider.dart index 3d97f2173f..39cf4cc3ff 100644 --- a/mobile/lib/presentation/pages/edit/editor.provider.dart +++ b/mobile/lib/presentation/pages/edit/editor.provider.dart @@ -1,10 +1,14 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/aspect_ratios.dart'; import 'package:immich_mobile/domain/models/asset_edit.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/utils/editor.utils.dart'; +part 'editor.provider.freezed.dart'; + final editorStateProvider = NotifierProvider(EditorProvider.new); class EditorProvider extends Notifier { @@ -32,8 +36,8 @@ class EditorProvider extends Notifier { final transform = normalizeTransformEdits(edits); state = state.copyWith( - originalWidth: originalWidth, - originalHeight: originalHeight, + originalWidth: originalWidth ?? state.originalWidth, + originalHeight: originalHeight ?? state.originalHeight, crop: crop, flipHorizontal: transform.mirrorHorizontal, flipVertical: transform.mirrorVertical, @@ -106,104 +110,24 @@ class EditorProvider extends Notifier { } } -class EditorState { - final bool isApplyingEdits; +@freezed +abstract class EditorState with _$EditorState { + const EditorState._(); - final int rotationAngle; - final bool flipHorizontal; - final bool flipVertical; - final Rect crop; - final CropAspectRatio aspectRatio; - - final int originalWidth; - final int originalHeight; - - final Duration animationDuration; - - final bool hasUnsavedEdits; - - const EditorState({ - bool? isApplyingEdits, - int? rotationAngle, - bool? flipHorizontal, - bool? flipVertical, - Rect? crop, - CropAspectRatio? aspectRatio, - int? originalWidth, - int? originalHeight, - Duration? animationDuration, - bool? hasUnsavedEdits, - }) : isApplyingEdits = isApplyingEdits ?? false, - rotationAngle = rotationAngle ?? 0, - flipHorizontal = flipHorizontal ?? false, - flipVertical = flipVertical ?? false, - animationDuration = animationDuration ?? Duration.zero, - originalWidth = originalWidth ?? 0, - originalHeight = originalHeight ?? 0, - crop = crop ?? const Rect.fromLTRB(0, 0, 1, 1), - aspectRatio = aspectRatio ?? CropAspectRatio.free, - hasUnsavedEdits = hasUnsavedEdits ?? false; - - EditorState copyWith({ - bool? isApplyingEdits, - int? rotationAngle, - bool? flipHorizontal, - bool? flipVertical, - CropAspectRatio? aspectRatio, - int? originalWidth, - int? originalHeight, - Duration? animationDuration, - Rect? crop, - bool? hasUnsavedEdits, - }) { - return EditorState( - isApplyingEdits: isApplyingEdits ?? this.isApplyingEdits, - rotationAngle: rotationAngle ?? this.rotationAngle, - flipHorizontal: flipHorizontal ?? this.flipHorizontal, - flipVertical: flipVertical ?? this.flipVertical, - aspectRatio: aspectRatio ?? this.aspectRatio, - animationDuration: animationDuration ?? this.animationDuration, - originalWidth: originalWidth ?? this.originalWidth, - originalHeight: originalHeight ?? this.originalHeight, - crop: crop ?? this.crop, - hasUnsavedEdits: hasUnsavedEdits ?? this.hasUnsavedEdits, - ); - } + const factory EditorState({ + @Default(false) bool isApplyingEdits, + @Default(0) int rotationAngle, + @Default(false) bool flipHorizontal, + @Default(false) bool flipVertical, + @Default(Rect.fromLTRB(0, 0, 1, 1)) Rect crop, + @Default(CropAspectRatio.free) CropAspectRatio aspectRatio, + @Default(0) int originalWidth, + @Default(0) int originalHeight, + @Default(Duration.zero) Duration animationDuration, + @Default(false) bool hasUnsavedEdits, + }) = _EditorState; bool get hasEdits { return rotationAngle != 0 || flipHorizontal || flipVertical || crop != const Rect.fromLTRB(0, 0, 1, 1); } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - - return other is EditorState && - other.isApplyingEdits == isApplyingEdits && - other.rotationAngle == rotationAngle && - other.flipHorizontal == flipHorizontal && - other.flipVertical == flipVertical && - other.crop == crop && - other.aspectRatio == aspectRatio && - other.originalWidth == originalWidth && - other.originalHeight == originalHeight && - other.animationDuration == animationDuration && - other.hasUnsavedEdits == hasUnsavedEdits; - } - - @override - int get hashCode { - return isApplyingEdits.hashCode ^ - rotationAngle.hashCode ^ - flipHorizontal.hashCode ^ - flipVertical.hashCode ^ - crop.hashCode ^ - aspectRatio.hashCode ^ - originalWidth.hashCode ^ - originalHeight.hashCode ^ - animationDuration.hashCode ^ - hasUnsavedEdits.hashCode; - } } diff --git a/mobile/lib/presentation/widgets/timeline/timeline.state.dart b/mobile/lib/presentation/widgets/timeline/timeline.state.dart index b39c431b43..bcdc3b4783 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.state.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.state.dart @@ -1,5 +1,7 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/constants.dart'; @@ -8,6 +10,8 @@ import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart'; import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +part 'timeline.state.freezed.dart'; + class TimelineArgs { final double maxWidth; final double maxHeight; @@ -49,25 +53,13 @@ class TimelineArgs { groupBy.hashCode; } -class TimelineState { - final bool isScrubbing; - final bool isScrolling; +@freezed +abstract class TimelineState with _$TimelineState { + const TimelineState._(); - const TimelineState({this.isScrubbing = false, this.isScrolling = false}); + const factory TimelineState({@Default(false) bool isScrubbing, @Default(false) bool isScrolling}) = _TimelineState; bool get isInteracting => isScrubbing || isScrolling; - - @override - bool operator ==(covariant TimelineState other) { - return isScrubbing == other.isScrubbing && isScrolling == other.isScrolling; - } - - @override - int get hashCode => isScrubbing.hashCode ^ isScrolling.hashCode; - - TimelineState copyWith({bool? isScrubbing, bool? isScrolling}) { - return TimelineState(isScrubbing: isScrubbing ?? this.isScrubbing, isScrolling: isScrolling ?? this.isScrolling); - } } class TimelineStateNotifier extends Notifier { diff --git a/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart b/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart index 7b1d5d2caa..fd2a6aebd9 100644 --- a/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart +++ b/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart @@ -1,81 +1,25 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -class AssetViewerState { - final double backgroundOpacity; - final bool showingDetails; - final bool showingControls; - final bool isZoomed; - final bool showingOcr; - final BaseAsset? currentAsset; - final int stackIndex; +part 'asset_viewer.provider.freezed.dart'; - const AssetViewerState({ - this.backgroundOpacity = 1.0, - this.showingDetails = false, - this.showingControls = true, - this.isZoomed = false, - this.showingOcr = false, - this.currentAsset, - this.stackIndex = 0, - }); - - AssetViewerState copyWith({ - double? backgroundOpacity, - bool? showingDetails, - bool? showingControls, - bool? isZoomed, - bool? showingOcr, +@freezed +abstract class AssetViewerState with _$AssetViewerState { + const factory AssetViewerState({ + @Default(1.0) double backgroundOpacity, + @Default(false) bool showingDetails, + @Default(true) bool showingControls, + @Default(false) bool isZoomed, + @Default(false) bool showingOcr, BaseAsset? currentAsset, - int? stackIndex, - }) { - return AssetViewerState( - backgroundOpacity: backgroundOpacity ?? this.backgroundOpacity, - showingDetails: showingDetails ?? this.showingDetails, - showingControls: showingControls ?? this.showingControls, - isZoomed: isZoomed ?? this.isZoomed, - showingOcr: showingOcr ?? this.showingOcr, - currentAsset: currentAsset ?? this.currentAsset, - stackIndex: stackIndex ?? this.stackIndex, - ); - } - - @override - String toString() { - return 'AssetViewerState(opacity: $backgroundOpacity, showingDetails: $showingDetails, controls: $showingControls, isZoomed: $isZoomed, showingOcr: $showingOcr)'; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - return other is AssetViewerState && - other.backgroundOpacity == backgroundOpacity && - other.showingDetails == showingDetails && - other.showingControls == showingControls && - other.isZoomed == isZoomed && - other.showingOcr == showingOcr && - other.currentAsset == currentAsset && - other.stackIndex == stackIndex; - } - - @override - int get hashCode => - backgroundOpacity.hashCode ^ - showingDetails.hashCode ^ - showingControls.hashCode ^ - isZoomed.hashCode ^ - showingOcr.hashCode ^ - currentAsset.hashCode ^ - stackIndex.hashCode; + @Default(0) int stackIndex, + }) = _AssetViewerState; } class AssetViewerStateNotifier extends Notifier { diff --git a/mobile/lib/providers/backup/drift_backup.provider.dart b/mobile/lib/providers/backup/drift_backup.provider.dart index b25bc360ff..1756d0fd30 100644 --- a/mobile/lib/providers/backup/drift_backup.provider.dart +++ b/mobile/lib/providers/backup/drift_backup.provider.dart @@ -1,6 +1,7 @@ import 'dart:async'; -import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; @@ -12,6 +13,8 @@ import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:immich_mobile/utils/upload_speed_calculator.dart'; import 'package:logging/logging.dart'; +part 'drift_backup.provider.freezed.dart'; + class EnqueueStatus { final int enqueueCount; final int totalCount; @@ -26,75 +29,17 @@ class EnqueueStatus { String toString() => 'EnqueueStatus(enqueueCount: $enqueueCount, totalCount: $totalCount)'; } -class DriftUploadStatus { - final String taskId; - final String filename; - final double progress; - final int fileSize; - final String networkSpeedAsString; - final bool? isFailed; - final String? error; - - const DriftUploadStatus({ - required this.taskId, - required this.filename, - required this.progress, - required this.fileSize, - required this.networkSpeedAsString, - this.isFailed, - this.error, - }); - - DriftUploadStatus copyWith({ - String? taskId, - String? filename, - double? progress, - int? fileSize, - String? networkSpeedAsString, +@freezed +abstract class DriftUploadStatus with _$DriftUploadStatus { + const factory DriftUploadStatus({ + required String taskId, + required String filename, + required double progress, + required int fileSize, + required String networkSpeedAsString, bool? isFailed, String? error, - }) { - return DriftUploadStatus( - taskId: taskId ?? this.taskId, - filename: filename ?? this.filename, - progress: progress ?? this.progress, - fileSize: fileSize ?? this.fileSize, - networkSpeedAsString: networkSpeedAsString ?? this.networkSpeedAsString, - isFailed: isFailed ?? this.isFailed, - error: error ?? this.error, - ); - } - - @override - String toString() { - return 'DriftUploadStatus(taskId: $taskId, filename: $filename, progress: $progress, fileSize: $fileSize, networkSpeedAsString: $networkSpeedAsString, isFailed: $isFailed, error: $error)'; - } - - @override - bool operator ==(covariant DriftUploadStatus other) { - if (identical(this, other)) { - return true; - } - - return other.taskId == taskId && - other.filename == filename && - other.progress == progress && - other.fileSize == fileSize && - other.networkSpeedAsString == networkSpeedAsString && - other.isFailed == isFailed && - other.error == error; - } - - @override - int get hashCode { - return taskId.hashCode ^ - filename.hashCode ^ - progress.hashCode ^ - fileSize.hashCode ^ - networkSpeedAsString.hashCode ^ - isFailed.hashCode ^ - error.hashCode; - } + }) = _DriftUploadStatus; } enum BackupError { none, syncFailed } diff --git a/mobile/lib/providers/server_info.provider.dart b/mobile/lib/providers/server_info.provider.dart index c25e496a04..79593cce95 100644 --- a/mobile/lib/providers/server_info.provider.dart +++ b/mobile/lib/providers/server_info.provider.dart @@ -59,7 +59,7 @@ class ServerInfoNotifier extends StateNotifier { } Future _checkServerVersionMismatch(ServerVersion serverVersion, {ServerVersion? latestVersion}) async { - state = state.copyWith(serverVersion: serverVersion, latestVersion: latestVersion); + state = state.copyWith(serverVersion: serverVersion, latestVersion: latestVersion ?? state.latestVersion); final packageInfo = await PackageInfo.fromPlatform(); final SemVer clientVersion = SemVer.fromString(packageInfo.version); diff --git a/mobile/lib/providers/sync_status.provider.dart b/mobile/lib/providers/sync_status.provider.dart index b7d4f8bdc3..7a70514ca7 100644 --- a/mobile/lib/providers/sync_status.provider.dart +++ b/mobile/lib/providers/sync_status.provider.dart @@ -1,6 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +part 'sync_status.provider.freezed.dart'; + enum SyncStatus { idle, syncing, @@ -17,58 +21,22 @@ enum SyncStatus { } } -class SyncStatusState { - final SyncStatus remoteSyncStatus; - final SyncStatus localSyncStatus; - final SyncStatus hashJobStatus; - final SyncStatus cloudIdSyncStatus; +@freezed +abstract class SyncStatusState with _$SyncStatusState { + const SyncStatusState._(); - final String? errorMessage; - - const SyncStatusState({ - this.remoteSyncStatus = SyncStatus.idle, - this.localSyncStatus = SyncStatus.idle, - this.hashJobStatus = SyncStatus.idle, - this.cloudIdSyncStatus = SyncStatus.idle, - this.errorMessage, - }); - - SyncStatusState copyWith({ - SyncStatus? remoteSyncStatus, - SyncStatus? localSyncStatus, - SyncStatus? hashJobStatus, - SyncStatus? cloudIdSyncStatus, + const factory SyncStatusState({ + @Default(SyncStatus.idle) SyncStatus remoteSyncStatus, + @Default(SyncStatus.idle) SyncStatus localSyncStatus, + @Default(SyncStatus.idle) SyncStatus hashJobStatus, + @Default(SyncStatus.idle) SyncStatus cloudIdSyncStatus, String? errorMessage, - }) { - return SyncStatusState( - remoteSyncStatus: remoteSyncStatus ?? this.remoteSyncStatus, - localSyncStatus: localSyncStatus ?? this.localSyncStatus, - hashJobStatus: hashJobStatus ?? this.hashJobStatus, - cloudIdSyncStatus: cloudIdSyncStatus ?? this.cloudIdSyncStatus, - errorMessage: errorMessage ?? this.errorMessage, - ); - } + }) = _SyncStatusState; bool get isRemoteSyncing => remoteSyncStatus == SyncStatus.syncing; bool get isLocalSyncing => localSyncStatus == SyncStatus.syncing; bool get isHashing => hashJobStatus == SyncStatus.syncing; bool get isCloudIdSyncing => cloudIdSyncStatus == SyncStatus.syncing; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - return other is SyncStatusState && - other.remoteSyncStatus == remoteSyncStatus && - other.localSyncStatus == localSyncStatus && - other.hashJobStatus == hashJobStatus && - other.cloudIdSyncStatus == cloudIdSyncStatus && - other.errorMessage == errorMessage; - } - - @override - int get hashCode => Object.hash(remoteSyncStatus, localSyncStatus, hashJobStatus, cloudIdSyncStatus, errorMessage); } class SyncStatusNotifier extends Notifier { @@ -88,7 +56,11 @@ class SyncStatusNotifier extends Notifier { /// void setRemoteSyncStatus(SyncStatus status, [String? errorMessage]) { - state = state.copyWith(remoteSyncStatus: status, errorMessage: status == SyncStatus.error ? errorMessage : null); + // TODO(agg23): These error messages probably should be cleared, not preserved on null + state = state.copyWith( + remoteSyncStatus: status, + errorMessage: (status == SyncStatus.error ? errorMessage : null) ?? state.errorMessage, + ); } void startRemoteSync() => setRemoteSyncStatus(SyncStatus.syncing); @@ -100,7 +72,11 @@ class SyncStatusNotifier extends Notifier { /// void setLocalSyncStatus(SyncStatus status, [String? errorMessage]) { - state = state.copyWith(localSyncStatus: status, errorMessage: status == SyncStatus.error ? errorMessage : null); + // TODO(agg23): These error messages probably should be cleared, not preserved on null + state = state.copyWith( + localSyncStatus: status, + errorMessage: (status == SyncStatus.error ? errorMessage : null) ?? state.errorMessage, + ); } void startLocalSync() => setLocalSyncStatus(SyncStatus.syncing); @@ -112,7 +88,11 @@ class SyncStatusNotifier extends Notifier { /// void setHashJobStatus(SyncStatus status, [String? errorMessage]) { - state = state.copyWith(hashJobStatus: status, errorMessage: status == SyncStatus.error ? errorMessage : null); + // TODO(agg23): These error messages probably should be cleared, not preserved on null + state = state.copyWith( + hashJobStatus: status, + errorMessage: (status == SyncStatus.error ? errorMessage : null) ?? state.errorMessage, + ); } void startHashJob() => setHashJobStatus(SyncStatus.syncing); @@ -124,7 +104,11 @@ class SyncStatusNotifier extends Notifier { /// void setCloudIdSyncStatus(SyncStatus status, [String? errorMessage]) { - state = state.copyWith(cloudIdSyncStatus: status, errorMessage: status == SyncStatus.error ? errorMessage : null); + // TODO(agg23): These error messages probably should be cleared, not preserved on null + state = state.copyWith( + cloudIdSyncStatus: status, + errorMessage: (status == SyncStatus.error ? errorMessage : null) ?? state.errorMessage, + ); } void startCloudIdSync() => setCloudIdSyncStatus(SyncStatus.syncing); diff --git a/mobile/lib/providers/websocket.provider.dart b/mobile/lib/providers/websocket.provider.dart index e7403c043c..32531c354b 100644 --- a/mobile/lib/providers/websocket.provider.dart +++ b/mobile/lib/providers/websocket.provider.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; @@ -15,30 +17,11 @@ import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; import 'package:socket_io_client/socket_io_client.dart'; -class WebsocketState { - final Socket? socket; - final bool isConnected; +part 'websocket.provider.freezed.dart'; - const WebsocketState({this.socket, required this.isConnected}); - - WebsocketState copyWith({Socket? socket, bool? isConnected}) { - return WebsocketState(socket: socket ?? this.socket, isConnected: isConnected ?? this.isConnected); - } - - @override - String toString() => 'WebsocketState(socket: $socket, isConnected: $isConnected)'; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - - return other is WebsocketState && other.socket == socket && other.isConnected == isConnected; - } - - @override - int get hashCode => socket.hashCode ^ isConnected.hashCode; +@freezed +abstract class WebsocketState with _$WebsocketState { + const factory WebsocketState({Socket? socket, required bool isConnected}) = _WebsocketState; } class WebsocketNotifier extends StateNotifier { diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index e0198f2683..f02df0b622 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -667,6 +667,22 @@ 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: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index a55bcf515b..c2848e6dd8 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -91,6 +91,7 @@ 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 @@ -104,6 +105,7 @@ dev_dependencies: flutter_native_splash: ^2.4.7 flutter_test: sdk: flutter + freezed: ^3.2.5 integration_test: sdk: flutter mocktail: ^1.0.5