From 235daff56125c5d7d5727f9b01f7a838dab0124e Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Mon, 10 Aug 2026 14:12:45 -0700 Subject: [PATCH] chore(mobile): more complicated Freezed pass (#30452) * chore(mobile): example freezed implementation on some models --- mobile/lib/constants/aspect_ratios.dart | 31 +-- mobile/lib/domain/models/log.model.dart | 61 ++---- mobile/lib/domain/models/map.model.dart | 21 +- mobile/lib/domain/models/memory.model.dart | 163 ++------------ mobile/lib/domain/models/person.model.dart | 97 +-------- .../domain/models/search_result.model.dart | 26 +-- mobile/lib/domain/models/tag.model.dart | 28 +-- mobile/lib/domain/models/user.model.dart | 86 ++------ .../domain/models/user_metadata.model.dart | 201 ++---------------- .../models/auth/auxilary_endpoint.model.dart | 77 ++----- .../models/auth/biometric_status.model.dart | 34 +-- .../lib/models/cast/cast_manager_state.dart | 91 ++------ .../models/download/download_state.model.dart | 108 ++-------- .../search/search_curated_content.model.dart | 74 ++----- .../models/search/search_filter.model.dart | 53 +---- .../actions/similar_photos.action.dart | 2 +- .../pages/search/drift_search.page.dart | 10 +- .../widgets/timeline/timeline.state.dart | 50 +---- .../timeline/timeline_drag_region.dart | 23 +- .../backup/drift_backup.provider.dart | 91 ++------ .../infrastructure/remote_album.provider.dart | 27 +-- .../search/search_filter.provider.dart | 37 +--- .../timeline/multiselect.provider.dart | 48 +---- .../upload_profile_image.provider.dart | 56 +---- .../services/background_upload.service.dart | 44 ++-- .../search/search_filter/camera_picker.dart | 4 +- 26 files changed, 272 insertions(+), 1271 deletions(-) diff --git a/mobile/lib/constants/aspect_ratios.dart b/mobile/lib/constants/aspect_ratios.dart index 9ad7b8c739..49165e0913 100644 --- a/mobile/lib/constants/aspect_ratios.dart +++ b/mobile/lib/constants/aspect_ratios.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; -class CropAspectRatio { - final int? numerator; - final int? denominator; +part 'aspect_ratios.freezed.dart'; - final String? customLabel; - final IconData? icon; +@freezed +abstract class CropAspectRatio with _$CropAspectRatio { + const CropAspectRatio._(); - const CropAspectRatio({this.numerator, this.denominator, this.customLabel, this.icon}); + const factory CropAspectRatio({int? numerator, int? denominator, String? customLabel, IconData? icon}) = + _CropAspectRatio; static const free = CropAspectRatio(customLabel: "Free", icon: Icons.crop_free); static const original = CropAspectRatio(customLabel: "Original", icon: Icons.crop_original); @@ -22,24 +23,6 @@ class CropAspectRatio { CropAspectRatio get flipped { return CropAspectRatio(numerator: denominator, denominator: numerator, customLabel: customLabel, icon: icon); } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - - return other is CropAspectRatio && - other.numerator == numerator && - other.denominator == denominator && - other.customLabel == customLabel && - other.icon == icon; - } - - @override - int get hashCode { - return numerator.hashCode ^ denominator.hashCode ^ customLabel.hashCode ^ icon.hashCode; - } } const aspectRatioFree = CropAspectRatio.free; diff --git a/mobile/lib/domain/models/log.model.dart b/mobile/lib/domain/models/log.model.dart index bed1729f9d..72267bcb5c 100644 --- a/mobile/lib/domain/models/log.model.dart +++ b/mobile/lib/domain/models/log.model.dart @@ -1,51 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'log.model.freezed.dart'; + /// Log levels according to dart logging [Level] enum LogLevel { all, finest, finer, fine, config, info, warning, severe, shout, off } -class LogMessage { - final String message; - final LogLevel level; - final DateTime createdAt; - final String? logger; - final String? error; - final String? stack; - - const LogMessage({ - required this.message, - required this.level, - required this.createdAt, - this.logger, - this.error, - this.stack, - }); - - @override - bool operator ==(covariant LogMessage other) { - if (identical(this, other)) { - return true; - } - - return other.message == message && - other.level == level && - other.createdAt == createdAt && - other.logger == logger && - other.error == error && - other.stack == stack; - } - - @override - int get hashCode { - return message.hashCode ^ level.hashCode ^ createdAt.hashCode ^ logger.hashCode ^ error.hashCode ^ stack.hashCode; - } - - @override - String toString() { - return '''LogMessage: { -message: $message, -level: $level, -createdAt: $createdAt, -logger: ${logger ?? ''}, -error: ${error ?? ''}, -stack: ${stack ?? ''}, -}'''; - } +@freezed +abstract class LogMessage with _$LogMessage { + const factory LogMessage({ + required String message, + required LogLevel level, + required DateTime createdAt, + String? logger, + String? error, + String? stack, + }) = _LogMessage; } diff --git a/mobile/lib/domain/models/map.model.dart b/mobile/lib/domain/models/map.model.dart index b55f176bfd..52f6fe8afd 100644 --- a/mobile/lib/domain/models/map.model.dart +++ b/mobile/lib/domain/models/map.model.dart @@ -1,20 +1,9 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; -class Marker { - final LatLng location; - final String assetId; +part 'map.model.freezed.dart'; - const Marker({required this.location, required this.assetId}); - - @override - bool operator ==(covariant Marker other) { - if (identical(this, other)) { - return true; - } - - return other.location == location && other.assetId == assetId; - } - - @override - int get hashCode => location.hashCode ^ assetId.hashCode; +@freezed +abstract class Marker with _$Marker { + const factory Marker({required LatLng location, required String assetId}) = _Marker; } diff --git a/mobile/lib/domain/models/memory.model.dart b/mobile/lib/domain/models/memory.model.dart index e786ca18b1..a75a4a4f0f 100644 --- a/mobile/lib/domain/models/memory.model.dart +++ b/mobile/lib/domain/models/memory.model.dart @@ -1,22 +1,22 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; -import 'package:collection/collection.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +part 'memory.model.freezed.dart'; + +// TODO(agg23): Remove enum suffix enum MemoryTypeEnum { // do not change this order! onThisDay, } -class MemoryData { - final int year; +@Freezed(fromJson: false, toJson: false) +abstract class MemoryData with _$MemoryData { + const MemoryData._(); - const MemoryData({required this.year}); - - MemoryData copyWith({int? year}) { - return MemoryData(year: year ?? this.year); - } + const factory MemoryData({required int year}) = _MemoryData; Map toMap() { return {'year': year}; @@ -29,144 +29,25 @@ class MemoryData { String toJson() => json.encode(toMap()); factory MemoryData.fromJson(String source) => MemoryData.fromMap(json.decode(source) as Map); - - @override - String toString() => 'MemoryData(year: $year)'; - - @override - bool operator ==(covariant MemoryData other) { - if (identical(this, other)) { - return true; - } - - return other.year == year; - } - - @override - int get hashCode => year.hashCode; } // Model for a memory stored in the server -class DriftMemory { - final String id; - final DateTime createdAt; - final DateTime updatedAt; - final DateTime? deletedAt; - final String ownerId; - - // enum - final MemoryTypeEnum type; - final MemoryData data; - final bool isSaved; - final DateTime memoryAt; - final DateTime? seenAt; - final DateTime? showAt; - final DateTime? hideAt; - final List assets; - - const DriftMemory({ - required this.id, - required this.createdAt, - required this.updatedAt, - this.deletedAt, - required this.ownerId, - required this.type, - required this.data, - required this.isSaved, - required this.memoryAt, - this.seenAt, - this.showAt, - this.hideAt, - required this.assets, - }); - - DriftMemory copyWith({ - String? id, - DateTime? createdAt, - DateTime? updatedAt, +// TODO(agg23): DriftMemoryRepository currently mutates `assets` +@Freezed(makeCollectionsUnmodifiable: false) +abstract class DriftMemory with _$DriftMemory { + const factory DriftMemory({ + required String id, + required DateTime createdAt, + required DateTime updatedAt, DateTime? deletedAt, - String? ownerId, - MemoryTypeEnum? type, - MemoryData? data, - bool? isSaved, - DateTime? memoryAt, + required String ownerId, + required MemoryTypeEnum type, + required MemoryData data, + required bool isSaved, + required DateTime memoryAt, DateTime? seenAt, DateTime? showAt, DateTime? hideAt, - List? assets, - }) { - return DriftMemory( - id: id ?? this.id, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedAt: deletedAt ?? this.deletedAt, - ownerId: ownerId ?? this.ownerId, - type: type ?? this.type, - data: data ?? this.data, - isSaved: isSaved ?? this.isSaved, - memoryAt: memoryAt ?? this.memoryAt, - seenAt: seenAt ?? this.seenAt, - showAt: showAt ?? this.showAt, - hideAt: hideAt ?? this.hideAt, - assets: assets ?? this.assets, - ); - } - - @override - String toString() { - return '''Memory { - id: $id, - createdAt: $createdAt, - updatedAt: $updatedAt, - deletedAt: ${deletedAt ?? ""}, - ownerId: $ownerId, - type: $type, - data: $data, - isSaved: $isSaved, - memoryAt: $memoryAt, - seenAt: ${seenAt ?? ""}, - showAt: ${showAt ?? ""}, - hideAt: ${hideAt ?? ""}, - assets: $assets -}'''; - } - - @override - bool operator ==(covariant DriftMemory other) { - if (identical(this, other)) { - return true; - } - final listEquals = const DeepCollectionEquality().equals; - - return other.id == id && - other.createdAt == createdAt && - other.updatedAt == updatedAt && - other.deletedAt == deletedAt && - other.ownerId == ownerId && - other.type == type && - other.data == data && - other.isSaved == isSaved && - other.memoryAt == memoryAt && - other.seenAt == seenAt && - other.showAt == showAt && - other.hideAt == hideAt && - listEquals(other.assets, assets); - } - - @override - int get hashCode { - return id.hashCode ^ - createdAt.hashCode ^ - updatedAt.hashCode ^ - deletedAt.hashCode ^ - ownerId.hashCode ^ - type.hashCode ^ - data.hashCode ^ - isSaved.hashCode ^ - memoryAt.hashCode ^ - seenAt.hashCode ^ - showAt.hashCode ^ - hideAt.hashCode ^ - assets.hashCode; - } + required List assets, + }) = _DriftMemory; } diff --git a/mobile/lib/domain/models/person.model.dart b/mobile/lib/domain/models/person.model.dart index 38d740eeb4..56e6d3990d 100644 --- a/mobile/lib/domain/models/person.model.dart +++ b/mobile/lib/domain/models/person.model.dart @@ -1,100 +1,19 @@ -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({ - required this.id, - this.birthDate, - required this.isHidden, - required this.name, - required this.thumbnailPath, - this.updatedAt, - }); - - final String id; - final DateTime? birthDate; - final bool isHidden; - final String name; - final String thumbnailPath; - final DateTime? updatedAt; - - @override - String toString() { - return 'Person(id: $id, birthDate: $birthDate, isHidden: $isHidden, name: $name, thumbnailPath: $thumbnailPath, updatedAt: $updatedAt)'; - } - - PersonDto copyWith({ - String? id, +@freezed +abstract class PersonDto with _$PersonDto { + const factory PersonDto({ + required String id, DateTime? birthDate, - bool? isHidden, - String? name, - String? thumbnailPath, + required bool isHidden, + required String name, + required String thumbnailPath, DateTime? updatedAt, - }) { - return PersonDto( - id: id ?? this.id, - birthDate: birthDate ?? this.birthDate, - isHidden: isHidden ?? this.isHidden, - name: name ?? this.name, - thumbnailPath: thumbnailPath ?? this.thumbnailPath, - updatedAt: updatedAt ?? this.updatedAt, - ); - } - - Map toMap() { - return { - 'id': id, - 'birthDate': birthDate?.millisecondsSinceEpoch, - 'isHidden': isHidden, - 'name': name, - 'thumbnailPath': thumbnailPath, - 'updatedAt': updatedAt?.millisecondsSinceEpoch, - }; - } - - factory PersonDto.fromMap(Map map) { - return PersonDto( - id: map['id'] as String, - birthDate: map['birthDate'] != null ? DateTime.fromMillisecondsSinceEpoch(map['birthDate'] as int) : null, - isHidden: map['isHidden'] as bool, - name: map['name'] as String, - thumbnailPath: map['thumbnailPath'] as String, - updatedAt: map['updatedAt'] != null ? DateTime.fromMillisecondsSinceEpoch(map['updatedAt'] as int) : null, - ); - } - - String toJson() => json.encode(toMap()); - - factory PersonDto.fromJson(String source) => PersonDto.fromMap(json.decode(source) as Map); - - @override - bool operator ==(covariant PersonDto other) { - if (identical(this, other)) { - return true; - } - - return other.id == id && - other.birthDate == birthDate && - other.isHidden == isHidden && - other.name == name && - other.thumbnailPath == thumbnailPath && - other.updatedAt == updatedAt; - } - - @override - int get hashCode { - return id.hashCode ^ - birthDate.hashCode ^ - isHidden.hashCode ^ - name.hashCode ^ - thumbnailPath.hashCode ^ - updatedAt.hashCode; - } + }) = _PersonDto; } // Model for a person stored in the server diff --git a/mobile/lib/domain/models/search_result.model.dart b/mobile/lib/domain/models/search_result.model.dart index 6a782e2f37..11409e64a2 100644 --- a/mobile/lib/domain/models/search_result.model.dart +++ b/mobile/lib/domain/models/search_result.model.dart @@ -1,25 +1,15 @@ -import 'package:collection/collection.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -class SearchResult { - final List assets; - final int? nextPage; +part 'search_result.model.freezed.dart'; - const SearchResult({required this.assets, this.nextPage}); +@Freezed(toStringOverride: false) +abstract class SearchResult with _$SearchResult { + const SearchResult._(); + const factory SearchResult({required List assets, int? nextPage}) = _SearchResult; + + // Explicitly don't log results, only attributes @override 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; - } - - @override - int get hashCode => assets.hashCode ^ nextPage.hashCode; } diff --git a/mobile/lib/domain/models/tag.model.dart b/mobile/lib/domain/models/tag.model.dart index ba9aef02ee..d38931a285 100644 --- a/mobile/lib/domain/models/tag.model.dart +++ b/mobile/lib/domain/models/tag.model.dart @@ -1,29 +1,11 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:openapi/api.dart'; -class Tag { - final String id; - final String value; +part 'tag.model.freezed.dart'; - const Tag({required this.id, required this.value}); - - @override - String toString() { - return 'Tag(id: $id, value: $value)'; - } - - @override - bool operator ==(covariant Tag other) { - if (identical(this, other)) { - return true; - } - - return other.id == id && other.value == value; - } - - @override - int get hashCode { - return id.hashCode ^ value.hashCode; - } +@freezed +abstract class Tag with _$Tag { + const factory Tag({required String id, required String value}) = _Tag; static Tag fromDto(TagResponseDto dto) { return Tag(id: dto.id, value: dto.value); diff --git a/mobile/lib/domain/models/user.model.dart b/mobile/lib/domain/models/user.model.dart index d1a7fc5546..96e07137c9 100644 --- a/mobile/lib/domain/models/user.model.dart +++ b/mobile/lib/domain/models/user.model.dart @@ -1,7 +1,10 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first -import 'dart:convert'; import 'dart:ui'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'user.model.freezed.dart'; + enum AvatarColor { // do not change this order or reuse indices for other purposes, adding is OK primary("primary"), @@ -164,78 +167,15 @@ profileChangedAt: $profileChangedAt quotaUsageInBytes.hashCode; } -class PartnerUserDto { - final String id; - final String email; - final String name; - final bool inTimeline; - - final String? profileImagePath; - - const PartnerUserDto({ - required this.id, - required this.email, - required this.name, - required this.inTimeline, - this.profileImagePath, - }); - - PartnerUserDto copyWith({String? id, String? email, String? name, bool? inTimeline, String? profileImagePath}) { - return PartnerUserDto( - id: id ?? this.id, - email: email ?? this.email, - name: name ?? this.name, - inTimeline: inTimeline ?? this.inTimeline, - profileImagePath: profileImagePath ?? this.profileImagePath, - ); - } - - Map toMap() { - return { - 'id': id, - 'email': email, - 'name': name, - 'inTimeline': inTimeline, - 'profileImagePath': profileImagePath, - }; - } - - factory PartnerUserDto.fromMap(Map map) { - return PartnerUserDto( - id: map['id'] as String, - email: map['email'] as String, - name: map['name'] as String, - inTimeline: map['inTimeline'] as bool, - profileImagePath: map['profileImagePath'] != null ? map['profileImagePath'] as String : null, - ); - } - - String toJson() => json.encode(toMap()); - - factory PartnerUserDto.fromJson(String source) => PartnerUserDto.fromMap(json.decode(source) as Map); - - @override - String toString() { - return 'PartnerUserDto(id: $id, email: $email, name: $name, inTimeline: $inTimeline, profileImagePath: $profileImagePath)'; - } - - @override - bool operator ==(covariant PartnerUserDto other) { - if (identical(this, other)) { - return true; - } - - return other.id == id && - other.email == email && - other.name == name && - other.inTimeline == inTimeline && - other.profileImagePath == profileImagePath; - } - - @override - int get hashCode { - return id.hashCode ^ email.hashCode ^ name.hashCode ^ inTimeline.hashCode ^ profileImagePath.hashCode; - } +@freezed +abstract class PartnerUserDto with _$PartnerUserDto { + const factory PartnerUserDto({ + required String id, + required String email, + required String name, + required bool inTimeline, + String? profileImagePath, + }) = _PartnerUserDto; } class User { diff --git a/mobile/lib/domain/models/user_metadata.model.dart b/mobile/lib/domain/models/user_metadata.model.dart index 8bce1aca13..2b1da0a809 100644 --- a/mobile/lib/domain/models/user_metadata.model.dart +++ b/mobile/lib/domain/models/user_metadata.model.dart @@ -11,105 +11,32 @@ enum UserMetadataKey { license, } -class Onboarding { - final bool isOnboarded; +@freezed +abstract class Onboarding with _$Onboarding { + const Onboarding._(); - const Onboarding({required this.isOnboarded}); - - Onboarding copyWith({bool? isOnboarded}) { - return Onboarding(isOnboarded: isOnboarded ?? this.isOnboarded); - } - - Map toMap() { - final onboarding = {}; - onboarding["isOnboarded"] = isOnboarded; - return onboarding; - } + const factory Onboarding({required bool isOnboarded}) = _Onboarding; factory Onboarding.fromMap(Map map) { return Onboarding(isOnboarded: map["isOnboarded"]! as bool); } - - @override - String toString() { - return '''Onboarding { -isOnboarded: $isOnboarded, -}'''; - } - - @override - bool operator ==(covariant Onboarding other) { - if (identical(this, other)) { - return true; - } - - return isOnboarded == other.isOnboarded; - } - - @override - int get hashCode => isOnboarded.hashCode; } -class Preferences { - final bool foldersEnabled; - final bool memoriesEnabled; - final bool peopleEnabled; - final bool ratingsEnabled; - final bool sharedLinksEnabled; - final bool tagsEnabled; - final AvatarColor userAvatarColor; - final bool showSupportBadge; - final int minimumFaces; +@freezed +abstract class Preferences with _$Preferences { + const Preferences._(); - const Preferences({ - this.foldersEnabled = false, - this.memoriesEnabled = true, - this.peopleEnabled = true, - this.ratingsEnabled = false, - this.sharedLinksEnabled = true, - this.tagsEnabled = false, - this.userAvatarColor = AvatarColor.primary, - this.showSupportBadge = true, - this.minimumFaces = 3, - }); - - Preferences copyWith({ - bool? foldersEnabled, - bool? memoriesEnabled, - bool? peopleEnabled, - bool? ratingsEnabled, - bool? sharedLinksEnabled, - bool? tagsEnabled, - AvatarColor? userAvatarColor, - bool? showSupportBadge, - int? minimumFaces, - }) { - return Preferences( - foldersEnabled: foldersEnabled ?? this.foldersEnabled, - memoriesEnabled: memoriesEnabled ?? this.memoriesEnabled, - peopleEnabled: peopleEnabled ?? this.peopleEnabled, - ratingsEnabled: ratingsEnabled ?? this.ratingsEnabled, - sharedLinksEnabled: sharedLinksEnabled ?? this.sharedLinksEnabled, - tagsEnabled: tagsEnabled ?? this.tagsEnabled, - userAvatarColor: userAvatarColor ?? this.userAvatarColor, - showSupportBadge: showSupportBadge ?? this.showSupportBadge, - minimumFaces: minimumFaces ?? this.minimumFaces, - ); - } - - Map toMap() { - final preferences = {}; - preferences["folders-Enabled"] = foldersEnabled; - preferences["memories-Enabled"] = memoriesEnabled; - preferences["people-Enabled"] = peopleEnabled; - preferences["ratings-Enabled"] = ratingsEnabled; - preferences["sharedLinks-Enabled"] = sharedLinksEnabled; - preferences["tags-Enabled"] = tagsEnabled; - preferences["avatar-Color"] = userAvatarColor.value; - preferences["purchase-ShowSupportBadge"] = showSupportBadge; - preferences["minimumFaces"] = minimumFaces; - return preferences; - } + const factory Preferences({ + @Default(false) bool foldersEnabled, + @Default(true) bool memoriesEnabled, + @Default(true) bool peopleEnabled, + @Default(false) bool ratingsEnabled, + @Default(true) bool sharedLinksEnabled, + @Default(false) bool tagsEnabled, + @Default(AvatarColor.primary) AvatarColor userAvatarColor, + @Default(true) bool showSupportBadge, + @Default(3) int minimumFaces, + }) = _Preferences; factory Preferences.fromMap(Map map) { return Preferences( @@ -127,75 +54,14 @@ class Preferences { minimumFaces: (map["people"] as Map?)?["minimumFaces"] as int? ?? 3, ); } - - @override - String toString() { - return '''Preferences: { -foldersEnabled: $foldersEnabled, -memoriesEnabled: $memoriesEnabled, -peopleEnabled: $peopleEnabled, -ratingsEnabled: $ratingsEnabled, -sharedLinksEnabled: $sharedLinksEnabled, -tagsEnabled: $tagsEnabled, -userAvatarColor: $userAvatarColor, -showSupportBadge: $showSupportBadge, -minimumFaces: $minimumFaces, -}'''; - } - - @override - bool operator ==(covariant Preferences other) { - if (identical(this, other)) { - return true; - } - - return other.foldersEnabled == foldersEnabled && - other.memoriesEnabled == memoriesEnabled && - other.peopleEnabled == peopleEnabled && - other.ratingsEnabled == ratingsEnabled && - other.sharedLinksEnabled == sharedLinksEnabled && - other.tagsEnabled == tagsEnabled && - other.userAvatarColor == userAvatarColor && - other.showSupportBadge == showSupportBadge && - other.minimumFaces == minimumFaces; - } - - @override - int get hashCode { - return foldersEnabled.hashCode ^ - memoriesEnabled.hashCode ^ - peopleEnabled.hashCode ^ - ratingsEnabled.hashCode ^ - sharedLinksEnabled.hashCode ^ - tagsEnabled.hashCode ^ - userAvatarColor.hashCode ^ - showSupportBadge.hashCode ^ - minimumFaces.hashCode; - } } -class License { - final DateTime activatedAt; - final String activationKey; - final String licenseKey; +@freezed +abstract class License with _$License { + const License._(); - const License({required this.activatedAt, required this.activationKey, required this.licenseKey}); - - License copyWith({DateTime? activatedAt, String? activationKey, String? licenseKey}) { - return License( - activatedAt: activatedAt ?? this.activatedAt, - activationKey: activationKey ?? this.activationKey, - licenseKey: licenseKey ?? this.licenseKey, - ); - } - - Map toMap() { - final license = {}; - license["activatedAt"] = activatedAt; - license["activationKey"] = activationKey; - license["licenseKey"] = licenseKey; - return license; - } + const factory License({required DateTime activatedAt, required String activationKey, required String licenseKey}) = + _License; factory License.fromMap(Map map) { return License( @@ -204,27 +70,6 @@ class License { licenseKey: map["licenseKey"]! as String, ); } - - @override - String toString() { - return '''License { -activatedAt: $activatedAt, -activationKey: $activationKey, -licenseKey: $licenseKey, -}'''; - } - - @override - bool operator ==(covariant License other) { - if (identical(this, other)) { - return true; - } - - return activatedAt == other.activatedAt && activationKey == other.activationKey && licenseKey == other.licenseKey; - } - - @override - int get hashCode => activatedAt.hashCode ^ activationKey.hashCode ^ licenseKey.hashCode; } // Model for a user metadata stored in the server diff --git a/mobile/lib/models/auth/auxilary_endpoint.model.dart b/mobile/lib/models/auth/auxilary_endpoint.model.dart index cd11918bed..8686ae9461 100644 --- a/mobile/lib/models/auth/auxilary_endpoint.model.dart +++ b/mobile/lib/models/auth/auxilary_endpoint.model.dart @@ -1,34 +1,14 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; -class AuxilaryEndpoint { - final String url; - final AuxCheckStatus status; +import 'package:freezed_annotation/freezed_annotation.dart'; - const AuxilaryEndpoint({required this.url, required this.status}); +part 'auxilary_endpoint.model.freezed.dart'; - AuxilaryEndpoint copyWith({String? url, AuxCheckStatus? status}) { - return AuxilaryEndpoint(url: url ?? this.url, status: status ?? this.status); - } +@Freezed(fromJson: false, toJson: false) +abstract class AuxilaryEndpoint with _$AuxilaryEndpoint { + const AuxilaryEndpoint._(); - @override - String toString() => 'AuxilaryEndpoint(url: $url, status: $status)'; - - @override - bool operator ==(covariant AuxilaryEndpoint other) { - if (identical(this, other)) { - return true; - } - - return other.url == url && other.status == status; - } - - @override - int get hashCode => url.hashCode ^ status.hashCode; - - Map toMap() { - return {'url': url, 'status': status.toMap()}; - } + const factory AuxilaryEndpoint({required String url, required AuxCheckStatus status}) = _AuxilaryEndpoint; factory AuxilaryEndpoint.fromMap(Map map) { return AuxilaryEndpoint( @@ -37,50 +17,23 @@ class AuxilaryEndpoint { ); } - String toJson() => json.encode(toMap()); - factory AuxilaryEndpoint.fromJson(String source) => AuxilaryEndpoint.fromMap(json.decode(source) as Map); } -class AuxCheckStatus { - final String name; - const AuxCheckStatus({required this.name}); - const AuxCheckStatus._(this.name); +// TODO(agg23): Should be an enum +@freezed +abstract class AuxCheckStatus with _$AuxCheckStatus { + const AuxCheckStatus._(); - static const loading = AuxCheckStatus._('loading'); - static const valid = AuxCheckStatus._('valid'); - static const error = AuxCheckStatus._('error'); - static const unknown = AuxCheckStatus._('unknown'); + const factory AuxCheckStatus({required String name}) = _AuxCheckStatus; - @override - bool operator ==(covariant AuxCheckStatus other) { - if (identical(this, other)) { - return true; - } - - return other.name == name; - } - - @override - int get hashCode => name.hashCode; - - AuxCheckStatus copyWith({String? name}) { - return AuxCheckStatus(name: name ?? this.name); - } - - Map toMap() { - return {'name': name}; - } + static const loading = AuxCheckStatus(name: 'loading'); + static const valid = AuxCheckStatus(name: 'valid'); + static const error = AuxCheckStatus(name: 'error'); + static const unknown = AuxCheckStatus(name: 'unknown'); factory AuxCheckStatus.fromMap(Map map) { return AuxCheckStatus(name: map['name'] as String); } - - String toJson() => json.encode(toMap()); - - factory AuxCheckStatus.fromJson(String source) => AuxCheckStatus.fromMap(json.decode(source) as Map); - - @override - String toString() => 'AuxCheckStatus(name: $name)'; } diff --git a/mobile/lib/models/auth/biometric_status.model.dart b/mobile/lib/models/auth/biometric_status.model.dart index c5c417d06d..691fee3468 100644 --- a/mobile/lib/models/auth/biometric_status.model.dart +++ b/mobile/lib/models/auth/biometric_status.model.dart @@ -1,32 +1,10 @@ -import 'package:collection/collection.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:local_auth/local_auth.dart'; -class BiometricStatus { - final List availableBiometrics; - final bool canAuthenticate; +part 'biometric_status.model.freezed.dart'; - const BiometricStatus({required this.availableBiometrics, required this.canAuthenticate}); - - @override - String toString() => 'BiometricStatus(availableBiometrics: $availableBiometrics, canAuthenticate: $canAuthenticate)'; - - BiometricStatus copyWith({List? availableBiometrics, bool? canAuthenticate}) { - return BiometricStatus( - availableBiometrics: availableBiometrics ?? this.availableBiometrics, - canAuthenticate: canAuthenticate ?? this.canAuthenticate, - ); - } - - @override - bool operator ==(covariant BiometricStatus other) { - if (identical(this, other)) { - return true; - } - final listEquals = const DeepCollectionEquality().equals; - - return listEquals(other.availableBiometrics, availableBiometrics) && other.canAuthenticate == canAuthenticate; - } - - @override - int get hashCode => availableBiometrics.hashCode ^ canAuthenticate.hashCode; +@freezed +abstract class BiometricStatus with _$BiometricStatus { + const factory BiometricStatus({required List availableBiometrics, required bool canAuthenticate}) = + _BiometricStatus; } diff --git a/mobile/lib/models/cast/cast_manager_state.dart b/mobile/lib/models/cast/cast_manager_state.dart index 9727bc7ed8..0fd3dc877b 100644 --- a/mobile/lib/models/cast/cast_manager_state.dart +++ b/mobile/lib/models/cast/cast_manager_state.dart @@ -1,85 +1,18 @@ -import 'dart:convert'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'cast_manager_state.freezed.dart'; enum CastDestinationType { googleCast } enum CastState { idle, playing, paused, buffering } -class CastManagerState { - final bool isCasting; - final String receiverName; - final CastState castState; - final Duration currentTime; - final Duration duration; - - const CastManagerState({ - required this.isCasting, - required this.receiverName, - required this.castState, - required this.currentTime, - required this.duration, - }); - - CastManagerState copyWith({ - bool? isCasting, - String? receiverName, - CastState? castState, - Duration? currentTime, - Duration? duration, - }) { - return CastManagerState( - isCasting: isCasting ?? this.isCasting, - receiverName: receiverName ?? this.receiverName, - castState: castState ?? this.castState, - currentTime: currentTime ?? this.currentTime, - duration: duration ?? this.duration, - ); - } - - Map toMap() { - final result = {}; - - result.addAll({'isCasting': isCasting}); - result.addAll({'receiverName': receiverName}); - result.addAll({'castState': castState}); - result.addAll({'currentTime': currentTime.inSeconds}); - result.addAll({'duration': duration.inSeconds}); - - return result; - } - - factory CastManagerState.fromMap(Map map) { - return CastManagerState( - isCasting: map['isCasting'] ?? false, - receiverName: map['receiverName'] ?? '', - castState: map['castState'] ?? CastState.idle, - currentTime: Duration(seconds: map['currentTime']?.toInt() ?? 0), - duration: Duration(seconds: map['duration']?.toInt() ?? 0), - ); - } - - String toJson() => json.encode(toMap()); - - factory CastManagerState.fromJson(String source) => CastManagerState.fromMap(json.decode(source)); - - @override - String toString() => - 'CastManagerState(isCasting: $isCasting, receiverName: $receiverName, castState: $castState, currentTime: $currentTime, duration: $duration)'; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - - return other is CastManagerState && - other.isCasting == isCasting && - other.receiverName == receiverName && - other.castState == castState && - other.currentTime == currentTime && - other.duration == duration; - } - - @override - int get hashCode => - isCasting.hashCode ^ receiverName.hashCode ^ castState.hashCode ^ currentTime.hashCode ^ duration.hashCode; +@freezed +abstract class CastManagerState with _$CastManagerState { + const factory CastManagerState({ + required bool isCasting, + required String receiverName, + required CastState castState, + required Duration currentTime, + required Duration duration, + }) = _CastManagerState; } diff --git a/mobile/lib/models/download/download_state.model.dart b/mobile/lib/models/download/download_state.model.dart index bed92d98b6..7e47d60969 100644 --- a/mobile/lib/models/download/download_state.model.dart +++ b/mobile/lib/models/download/download_state.model.dart @@ -1,88 +1,20 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first -import 'dart:convert'; - -import 'package:background_downloader/background_downloader.dart'; -import 'package:collection/collection.dart'; - -class DownloadInfo { - final String fileName; - final double progress; - // enum - final TaskStatus status; - - const DownloadInfo({required this.fileName, required this.progress, required this.status}); - - DownloadInfo copyWith({String? fileName, double? progress, TaskStatus? status}) { - return DownloadInfo( - fileName: fileName ?? this.fileName, - progress: progress ?? this.progress, - status: status ?? this.status, - ); - } - - Map toMap() { - return {'fileName': fileName, 'progress': progress, 'status': status.index}; - } - - factory DownloadInfo.fromMap(Map map) { - return DownloadInfo( - fileName: map['fileName'] as String, - progress: map['progress'] as double, - status: TaskStatus.values[map['status'] as int], - ); - } - - String toJson() => json.encode(toMap()); - - factory DownloadInfo.fromJson(String source) => DownloadInfo.fromMap(json.decode(source) as Map); - - @override - String toString() => 'DownloadInfo(fileName: $fileName, progress: $progress, status: $status)'; - - @override - bool operator ==(covariant DownloadInfo other) { - if (identical(this, other)) { - return true; - } - - return other.fileName == fileName && other.progress == progress && other.status == status; - } - - @override - int get hashCode => fileName.hashCode ^ progress.hashCode ^ status.hashCode; -} - -class DownloadState { - // enum - final TaskStatus downloadStatus; - final Map taskProgress; - final bool showProgress; - const DownloadState({required this.downloadStatus, required this.taskProgress, required this.showProgress}); - - DownloadState copyWith({TaskStatus? downloadStatus, Map? taskProgress, bool? showProgress}) { - return DownloadState( - downloadStatus: downloadStatus ?? this.downloadStatus, - taskProgress: taskProgress ?? this.taskProgress, - showProgress: showProgress ?? this.showProgress, - ); - } - - @override - String toString() => - 'DownloadState(downloadStatus: $downloadStatus, taskProgress: $taskProgress, showProgress: $showProgress)'; - - @override - bool operator ==(covariant DownloadState other) { - if (identical(this, other)) { - return true; - } - final mapEquals = const DeepCollectionEquality().equals; - - return other.downloadStatus == downloadStatus && - mapEquals(other.taskProgress, taskProgress) && - other.showProgress == showProgress; - } - - @override - int get hashCode => downloadStatus.hashCode ^ taskProgress.hashCode ^ showProgress.hashCode; -} +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:background_downloader/background_downloader.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'download_state.model.freezed.dart'; + +@freezed +abstract class DownloadInfo with _$DownloadInfo { + const factory DownloadInfo({required String fileName, required double progress, required TaskStatus status}) = + _DownloadInfo; +} + +@freezed +abstract class DownloadState with _$DownloadState { + const factory DownloadState({ + required TaskStatus downloadStatus, + required Map taskProgress, + required bool showProgress, + }) = _DownloadState; +} diff --git a/mobile/lib/models/search/search_curated_content.model.dart b/mobile/lib/models/search/search_curated_content.model.dart index 58c4c73264..8778c9b407 100644 --- a/mobile/lib/models/search/search_curated_content.model.dart +++ b/mobile/lib/models/search/search_curated_content.model.dart @@ -1,54 +1,20 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first -import 'dart:convert'; - -/// A wrapper for [CuratedLocationsResponseDto] objects -/// and [CuratedObjectsResponseDto] to be displayed in -/// a view -class SearchCuratedContent { - /// The label to show associated with this curated object - final String label; - - /// The subtitle to show below the label - final String? subtitle; - - /// The id to lookup the asset from the server - final String id; - - const SearchCuratedContent({required this.label, required this.id, this.subtitle}); - - SearchCuratedContent copyWith({String? label, String? subtitle, String? id}) { - return SearchCuratedContent(label: label ?? this.label, subtitle: subtitle ?? this.subtitle, id: id ?? this.id); - } - - Map toMap() { - return {'label': label, 'subtitle': subtitle, 'id': id}; - } - - factory SearchCuratedContent.fromMap(Map map) { - return SearchCuratedContent( - label: map['label'] as String, - subtitle: map['subtitle'] as String?, - id: map['id'] as String, - ); - } - - String toJson() => json.encode(toMap()); - - factory SearchCuratedContent.fromJson(String source) => - SearchCuratedContent.fromMap(json.decode(source) as Map); - - @override - String toString() => 'CuratedContent(label: $label, subtitle: $subtitle, id: $id)'; - - @override - bool operator ==(covariant SearchCuratedContent other) { - if (identical(this, other)) { - return true; - } - - return other.label == label && other.subtitle == subtitle && other.id == id; - } - - @override - int get hashCode => label.hashCode ^ id.hashCode; -} +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'search_curated_content.model.freezed.dart'; + +/// A wrapper for [CuratedLocationsResponseDto] objects +/// and [CuratedObjectsResponseDto] to be displayed in +/// a view +@freezed +abstract class SearchCuratedContent with _$SearchCuratedContent { + const factory SearchCuratedContent({ + /// The label to show associated with this curated object + required String label, + + /// The id to lookup the asset from the server + required String id, + + /// The subtitle to show below the label + String? subtitle, + }) = _SearchCuratedContent; +} diff --git a/mobile/lib/models/search/search_filter.model.dart b/mobile/lib/models/search/search_filter.model.dart index 825acb32c6..7e5e1b819e 100644 --- a/mobile/lib/models/search/search_filter.model.dart +++ b/mobile/lib/models/search/search_filter.model.dart @@ -1,10 +1,13 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/utils/option.dart'; +part 'search_filter.model.freezed.dart'; + class SearchLocationFilter { String? country; String? state; @@ -177,52 +180,10 @@ class SearchRatingFilter { int get hashCode => rating.hashCode; } -class SearchDisplayFilters { - bool isNotInAlbum = false; - bool isArchive = false; - bool isFavorite = false; - SearchDisplayFilters({required this.isNotInAlbum, required this.isArchive, required this.isFavorite}); - - SearchDisplayFilters copyWith({bool? isNotInAlbum, bool? isArchive, bool? isFavorite}) { - return SearchDisplayFilters( - isNotInAlbum: isNotInAlbum ?? this.isNotInAlbum, - isArchive: isArchive ?? this.isArchive, - isFavorite: isFavorite ?? this.isFavorite, - ); - } - - Map toMap() { - return {'isNotInAlbum': isNotInAlbum, 'isArchive': isArchive, 'isFavorite': isFavorite}; - } - - factory SearchDisplayFilters.fromMap(Map map) { - return SearchDisplayFilters( - isNotInAlbum: map['isNotInAlbum'] as bool, - isArchive: map['isArchive'] as bool, - isFavorite: map['isFavorite'] as bool, - ); - } - - String toJson() => json.encode(toMap()); - - factory SearchDisplayFilters.fromJson(String source) => - SearchDisplayFilters.fromMap(json.decode(source) as Map); - - @override - String toString() => - 'SearchDisplayFilters(isNotInAlbum: $isNotInAlbum, isArchive: $isArchive, isFavorite: $isFavorite)'; - - @override - bool operator ==(covariant SearchDisplayFilters other) { - if (identical(this, other)) { - return true; - } - - return other.isNotInAlbum == isNotInAlbum && other.isArchive == isArchive && other.isFavorite == isFavorite; - } - - @override - int get hashCode => isNotInAlbum.hashCode ^ isArchive.hashCode ^ isFavorite.hashCode; +@freezed +abstract class SearchDisplayFilters with _$SearchDisplayFilters { + const factory SearchDisplayFilters({required bool isNotInAlbum, required bool isArchive, required bool isFavorite}) = + _SearchDisplayFilters; } class SearchFilter { diff --git a/mobile/lib/presentation/actions/similar_photos.action.dart b/mobile/lib/presentation/actions/similar_photos.action.dart index 3c0b7b129b..d8f63f245d 100644 --- a/mobile/lib/presentation/actions/similar_photos.action.dart +++ b/mobile/lib/presentation/actions/similar_photos.action.dart @@ -31,7 +31,7 @@ class SimilarPhotosAction extends ActionBuilder { location: .new(), camera: .new(), date: .new(), - display: .new(isNotInAlbum: false, isArchive: false, isFavorite: false), + display: const .new(isNotInAlbum: false, isArchive: false, isFavorite: false), rating: .new(), mediaType: .other, ), diff --git a/mobile/lib/presentation/pages/search/drift_search.page.dart b/mobile/lib/presentation/pages/search/drift_search.page.dart index 12935b4f23..63e48e0c33 100644 --- a/mobile/lib/presentation/pages/search/drift_search.page.dart +++ b/mobile/lib/presentation/pages/search/drift_search.page.dart @@ -59,7 +59,7 @@ class DriftSearchPage extends HookConsumerWidget { location: SearchLocationFilter(), camera: SearchCameraFilter(), date: SearchDateFilter(), - display: SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), + display: const SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), rating: SearchRatingFilter(), mediaType: AssetType.other, language: "${context.locale.languageCode}-${context.locale.countryCode}", @@ -455,9 +455,9 @@ class DriftSearchPage extends HookConsumerWidget { void handleOnSelect(Map value) { display = display.copyWith( - isNotInAlbum: value[DisplayOption.notInAlbum], - isArchive: value[DisplayOption.archive], - isFavorite: value[DisplayOption.favorite], + isNotInAlbum: value[DisplayOption.notInAlbum] ?? display.isNotInAlbum, + isArchive: value[DisplayOption.archive] ?? display.isArchive, + isFavorite: value[DisplayOption.favorite] ?? display.isFavorite, ); } @@ -465,7 +465,7 @@ class DriftSearchPage extends HookConsumerWidget { displayOptionCurrentFilterWidget.value = null; search( filter.value.copyWith( - display: SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), + display: const SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), ), ); } diff --git a/mobile/lib/presentation/widgets/timeline/timeline.state.dart b/mobile/lib/presentation/widgets/timeline/timeline.state.dart index bcdc3b4783..adad06b987 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.state.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.state.dart @@ -12,45 +12,17 @@ import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; part 'timeline.state.freezed.dart'; -class TimelineArgs { - final double maxWidth; - final double maxHeight; - final double spacing; - final int columnCount; - final bool showStorageIndicator; - final bool withStack; - final GroupAssetsBy? groupBy; - - const TimelineArgs({ - required this.maxWidth, - required this.maxHeight, - this.spacing = kTimelineSpacing, - this.columnCount = kTimelineColumnCount, - this.showStorageIndicator = false, - this.withStack = false, - this.groupBy, - }); - - @override - bool operator ==(covariant TimelineArgs other) { - return spacing == other.spacing && - maxWidth == other.maxWidth && - maxHeight == other.maxHeight && - columnCount == other.columnCount && - showStorageIndicator == other.showStorageIndicator && - withStack == other.withStack && - groupBy == other.groupBy; - } - - @override - int get hashCode => - maxWidth.hashCode ^ - maxHeight.hashCode ^ - spacing.hashCode ^ - columnCount.hashCode ^ - showStorageIndicator.hashCode ^ - withStack.hashCode ^ - groupBy.hashCode; +@freezed +abstract class TimelineArgs with _$TimelineArgs { + const factory TimelineArgs({ + required double maxWidth, + required double maxHeight, + @Default(kTimelineSpacing) double spacing, + @Default(kTimelineColumnCount) int columnCount, + @Default(false) bool showStorageIndicator, + @Default(false) bool withStack, + GroupAssetsBy? groupBy, + }) = _TimelineArgs; } @freezed diff --git a/mobile/lib/presentation/widgets/timeline/timeline_drag_region.dart b/mobile/lib/presentation/widgets/timeline/timeline_drag_region.dart index 9ffcc3b23b..892d0c6102 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline_drag_region.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline_drag_region.dart @@ -4,6 +4,9 @@ import 'package:collection/collection.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'timeline_drag_region.freezed.dart'; class TimelineDragRegion extends StatefulWidget { final Widget child; @@ -206,21 +209,7 @@ class _TimelineAssetIndexProxy extends RenderProxyBox { _TimelineAssetIndexProxy({required this.index}); } -class TimelineAssetIndex { - final int assetIndex; - final int segmentIndex; - - const TimelineAssetIndex({required this.assetIndex, required this.segmentIndex}); - - @override - bool operator ==(covariant TimelineAssetIndex other) { - if (identical(this, other)) { - return true; - } - - return other.assetIndex == assetIndex && other.segmentIndex == segmentIndex; - } - - @override - int get hashCode => assetIndex.hashCode ^ segmentIndex.hashCode; +@freezed +abstract class TimelineAssetIndex with _$TimelineAssetIndex { + const factory TimelineAssetIndex({required int assetIndex, required int segmentIndex}) = _TimelineAssetIndex; } diff --git a/mobile/lib/providers/backup/drift_backup.provider.dart b/mobile/lib/providers/backup/drift_backup.provider.dart index 1756d0fd30..c3e1098e4b 100644 --- a/mobile/lib/providers/backup/drift_backup.provider.dart +++ b/mobile/lib/providers/backup/drift_backup.provider.dart @@ -44,87 +44,22 @@ abstract class DriftUploadStatus with _$DriftUploadStatus { enum BackupError { none, syncFailed } -class DriftBackupState { - final int totalCount; - final int backupCount; - final int remainderCount; - final int processingCount; +@freezed +abstract class DriftBackupState with _$DriftBackupState { + const DriftBackupState._(); - final bool isSyncing; - final BackupError error; - - final Map uploadItems; - - final Map iCloudDownloadProgress; - - const DriftBackupState({ - required this.totalCount, - required this.backupCount, - required this.remainderCount, - required this.processingCount, - required this.isSyncing, - this.error = BackupError.none, - required this.uploadItems, - this.iCloudDownloadProgress = const {}, - }); - - DriftBackupState copyWith({ - int? totalCount, - int? backupCount, - int? remainderCount, - int? processingCount, - bool? isSyncing, - BackupError? error, - Map? uploadItems, - Map? iCloudDownloadProgress, - }) { - return DriftBackupState( - totalCount: totalCount ?? this.totalCount, - backupCount: backupCount ?? this.backupCount, - remainderCount: remainderCount ?? this.remainderCount, - processingCount: processingCount ?? this.processingCount, - isSyncing: isSyncing ?? this.isSyncing, - error: error ?? this.error, - uploadItems: uploadItems ?? this.uploadItems, - iCloudDownloadProgress: iCloudDownloadProgress ?? this.iCloudDownloadProgress, - ); - } + const factory DriftBackupState({ + required int totalCount, + required int backupCount, + required int remainderCount, + required int processingCount, + required bool isSyncing, + @Default(BackupError.none) BackupError error, + required Map uploadItems, + @Default({}) Map iCloudDownloadProgress, + }) = _DriftBackupState; int get errorCount => uploadItems.values.where((item) => item.isFailed == true).length; - - @override - String toString() { - return 'DriftBackupState(totalCount: $totalCount, backupCount: $backupCount, remainderCount: $remainderCount, processingCount: $processingCount, isSyncing: $isSyncing, error: $error, uploadItems: $uploadItems, iCloudDownloadProgress: $iCloudDownloadProgress)'; - } - - @override - bool operator ==(covariant DriftBackupState other) { - if (identical(this, other)) { - return true; - } - final mapEquals = const DeepCollectionEquality().equals; - - return other.totalCount == totalCount && - other.backupCount == backupCount && - other.remainderCount == remainderCount && - other.processingCount == processingCount && - other.isSyncing == isSyncing && - other.error == error && - mapEquals(other.iCloudDownloadProgress, iCloudDownloadProgress) && - mapEquals(other.uploadItems, uploadItems); - } - - @override - int get hashCode { - return totalCount.hashCode ^ - backupCount.hashCode ^ - remainderCount.hashCode ^ - processingCount.hashCode ^ - isSyncing.hashCode ^ - error.hashCode ^ - uploadItems.hashCode ^ - iCloudDownloadProgress.hashCode; - } } final driftBackupProvider = StateNotifierProvider((ref) { diff --git a/mobile/lib/providers/infrastructure/remote_album.provider.dart b/mobile/lib/providers/infrastructure/remote_album.provider.dart index 4457b9f831..b76a8213c6 100644 --- a/mobile/lib/providers/infrastructure/remote_album.provider.dart +++ b/mobile/lib/providers/infrastructure/remote_album.provider.dart @@ -2,7 +2,7 @@ import 'dart:async'; -import 'package:collection/collection.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; @@ -17,30 +17,17 @@ import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:logging/logging.dart'; -class RemoteAlbumState { - final List albums; +part 'remote_album.provider.freezed.dart'; - const RemoteAlbumState({required this.albums}); +@Freezed(toStringOverride: false) +abstract class RemoteAlbumState with _$RemoteAlbumState { + const RemoteAlbumState._(); - RemoteAlbumState copyWith({List? albums}) { - return RemoteAlbumState(albums: albums ?? this.albums); - } + const factory RemoteAlbumState({required List albums}) = _RemoteAlbumState; + // Explicitly don't log albums @override String toString() => 'RemoteAlbumState(albums: ${albums.length})'; - - @override - bool operator ==(covariant RemoteAlbumState other) { - if (identical(this, other)) { - return true; - } - final listEquals = const DeepCollectionEquality().equals; - - return listEquals(other.albums, albums); - } - - @override - int get hashCode => albums.hashCode; } class RemoteAlbumNotifier extends Notifier { diff --git a/mobile/lib/providers/search/search_filter.provider.dart b/mobile/lib/providers/search/search_filter.provider.dart index b171de50c7..5da0a11fe7 100644 --- a/mobile/lib/providers/search/search_filter.provider.dart +++ b/mobile/lib/providers/search/search_filter.provider.dart @@ -1,34 +1,19 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/services/search.service.dart'; import 'package:openapi/api.dart'; -class SearchSuggestionArgs { - SearchSuggestionType type; - final String? locationCountry; - final String? locationState; - final String? make; - final String? model; +part 'search_filter.provider.freezed.dart'; - SearchSuggestionArgs({required this.type, this.locationCountry, this.locationState, this.make, this.model}); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - - return other is SearchSuggestionArgs && - other.type == type && - other.locationCountry == locationCountry && - other.locationState == locationState && - other.make == make && - other.model == model; - } - - @override - int get hashCode { - return type.hashCode ^ locationCountry.hashCode ^ locationState.hashCode ^ make.hashCode ^ model.hashCode; - } +@freezed +abstract class SearchSuggestionArgs with _$SearchSuggestionArgs { + const factory SearchSuggestionArgs({ + required SearchSuggestionType type, + String? locationCountry, + String? locationState, + String? make, + String? model, + }) = _SearchSuggestionArgs; } final getSearchSuggestionsProvider = FutureProvider.autoDispose.family, SearchSuggestionArgs>(( diff --git a/mobile/lib/providers/timeline/multiselect.provider.dart b/mobile/lib/providers/timeline/multiselect.provider.dart index 36de8d5e4f..f74201e983 100644 --- a/mobile/lib/providers/timeline/multiselect.provider.dart +++ b/mobile/lib/providers/timeline/multiselect.provider.dart @@ -1,22 +1,27 @@ // ignore_for_file: use-ref-and-state-synchronously -import 'package:collection/collection.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/domain/services/timeline.service.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +part 'multiselect.provider.freezed.dart'; + final multiSelectProvider = NotifierProvider( MultiSelectNotifier.new, dependencies: [timelineServiceProvider], ); -class MultiSelectState { - final Set selectedAssets; - final Set lockedSelectionAssets; - final bool forceEnable; +@freezed +abstract class MultiSelectState with _$MultiSelectState { + const MultiSelectState._(); - const MultiSelectState({required this.selectedAssets, required this.lockedSelectionAssets, this.forceEnable = false}); + const factory MultiSelectState({ + required Set selectedAssets, + required Set lockedSelectionAssets, + @Default(false) bool forceEnable, + }) = _MultiSelectState; bool get isEnabled => selectedAssets.isNotEmpty; @@ -29,37 +34,6 @@ class MultiSelectState { bool get onlyLocal => selectedAssets.any((asset) => asset.storage == AssetState.local); bool get onlyRemote => selectedAssets.any((asset) => asset.storage == AssetState.remote); - - MultiSelectState copyWith({ - Set? selectedAssets, - Set? lockedSelectionAssets, - bool? forceEnable, - }) { - return MultiSelectState( - selectedAssets: selectedAssets ?? this.selectedAssets, - lockedSelectionAssets: lockedSelectionAssets ?? this.lockedSelectionAssets, - forceEnable: forceEnable ?? this.forceEnable, - ); - } - - @override - String toString() => - 'MultiSelectState(selectedAssets: $selectedAssets, lockedSelectionAssets: $lockedSelectionAssets, forceEnable: $forceEnable)'; - - @override - bool operator ==(covariant MultiSelectState other) { - if (identical(this, other)) { - return true; - } - final setEquals = const DeepCollectionEquality().equals; - - return setEquals(other.selectedAssets, selectedAssets) && - setEquals(other.lockedSelectionAssets, lockedSelectionAssets) && - other.forceEnable == forceEnable; - } - - @override - int get hashCode => selectedAssets.hashCode ^ lockedSelectionAssets.hashCode ^ forceEnable.hashCode; } class MultiSelectNotifier extends Notifier { diff --git a/mobile/lib/providers/upload_profile_image.provider.dart b/mobile/lib/providers/upload_profile_image.provider.dart index 5e7f7fc732..9b5e83622e 100644 --- a/mobile/lib/providers/upload_profile_image.provider.dart +++ b/mobile/lib/providers/upload_profile_image.provider.dart @@ -1,60 +1,18 @@ -import 'dart:convert'; - +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/utils/debug_print.dart'; +part 'upload_profile_image.provider.freezed.dart'; + enum UploadProfileStatus { idle, loading, success, failure } -class UploadProfileImageState { - // enum - final UploadProfileStatus status; - final String profileImagePath; - const UploadProfileImageState({required this.status, required this.profileImagePath}); - - UploadProfileImageState copyWith({UploadProfileStatus? status, String? profileImagePath}) { - return UploadProfileImageState( - status: status ?? this.status, - profileImagePath: profileImagePath ?? this.profileImagePath, - ); - } - - Map toMap() { - final result = {}; - - result.addAll({'status': status.index}); - result.addAll({'profileImagePath': profileImagePath}); - - return result; - } - - factory UploadProfileImageState.fromMap(Map map) { - return UploadProfileImageState( - status: UploadProfileStatus.values[map['status'] ?? 0], - profileImagePath: map['profileImagePath'] ?? '', - ); - } - - String toJson() => json.encode(toMap()); - - factory UploadProfileImageState.fromJson(String source) => UploadProfileImageState.fromMap(json.decode(source)); - - @override - String toString() => 'UploadProfileImageState(status: $status, profileImagePath: $profileImagePath)'; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - - return other is UploadProfileImageState && other.status == status && other.profileImagePath == profileImagePath; - } - - @override - int get hashCode => status.hashCode ^ profileImagePath.hashCode; +@freezed +abstract class UploadProfileImageState with _$UploadProfileImageState { + const factory UploadProfileImageState({required UploadProfileStatus status, required String profileImagePath}) = + _UploadProfileImageState; } class UploadProfileImageNotifier extends StateNotifier { diff --git a/mobile/lib/services/background_upload.service.dart b/mobile/lib/services/background_upload.service.dart index 5312107f6c..b2a134389f 100644 --- a/mobile/lib/services/background_upload.service.dart +++ b/mobile/lib/services/background_upload.service.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:background_downloader/background_downloader.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/asset/asset_metadata.model.dart'; @@ -25,6 +26,8 @@ import 'package:logging/logging.dart'; import 'package:openapi/api.dart' as api; import 'package:path/path.dart' as p; +part 'background_upload.service.freezed.dart'; + final backgroundUploadServiceProvider = Provider((ref) { final service = BackgroundUploadService( ref.watch(uploadRepositoryProvider), @@ -39,20 +42,15 @@ final backgroundUploadServiceProvider = Provider((ref) { }); /// Metadata for upload tasks to track live photo handling -class UploadTaskMetadata { - final String localAssetId; - final bool isLivePhotos; - final String livePhotoVideoId; +@Freezed(fromJson: false, toJson: false) +abstract class UploadTaskMetadata with _$UploadTaskMetadata { + const UploadTaskMetadata._(); - const UploadTaskMetadata({required this.localAssetId, required this.isLivePhotos, required this.livePhotoVideoId}); - - UploadTaskMetadata copyWith({String? localAssetId, bool? isLivePhotos, String? livePhotoVideoId}) { - return UploadTaskMetadata( - localAssetId: localAssetId ?? this.localAssetId, - isLivePhotos: isLivePhotos ?? this.isLivePhotos, - livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, - ); - } + const factory UploadTaskMetadata({ + required String localAssetId, + required bool isLivePhotos, + required String livePhotoVideoId, + }) = _UploadTaskMetadata; Map toMap() { return { @@ -70,28 +68,10 @@ class UploadTaskMetadata { ); } - String toJson() => json.encode(toMap()); - factory UploadTaskMetadata.fromJson(String source) => UploadTaskMetadata.fromMap(json.decode(source) as Map); - @override - String toString() => - 'UploadTaskMetadata(localAssetId: $localAssetId, isLivePhotos: $isLivePhotos, livePhotoVideoId: $livePhotoVideoId)'; - - @override - bool operator ==(covariant UploadTaskMetadata other) { - if (identical(this, other)) { - return true; - } - - return other.localAssetId == localAssetId && - other.isLivePhotos == isLivePhotos && - other.livePhotoVideoId == livePhotoVideoId; - } - - @override - int get hashCode => localAssetId.hashCode ^ isLivePhotos.hashCode ^ livePhotoVideoId.hashCode; + String toJson() => json.encode(toMap()); } /// Service for handling background uploads using iOS URLSession (background_downloader) diff --git a/mobile/lib/widgets/search/search_filter/camera_picker.dart b/mobile/lib/widgets/search/search_filter/camera_picker.dart index 6a025bdb69..24a463eedb 100644 --- a/mobile/lib/widgets/search/search_filter/camera_picker.dart +++ b/mobile/lib/widgets/search/search_filter/camera_picker.dart @@ -21,7 +21,9 @@ class CameraPicker extends HookConsumerWidget { final selectedMake = useState(filter?.make); final selectedModel = useState(filter?.model); - final make = ref.watch(getSearchSuggestionsProvider(SearchSuggestionArgs(type: SearchSuggestionType.cameraMake))); + final make = ref.watch( + getSearchSuggestionsProvider(const SearchSuggestionArgs(type: SearchSuggestionType.cameraMake)), + ); final models = ref.watch( getSearchSuggestionsProvider(