chore(mobile): more complicated Freezed pass (#30452)

* chore(mobile): example freezed implementation on some models
This commit is contained in:
Adam Gastineau 2026-08-10 14:12:45 -07:00 committed by GitHub
parent 0ff47f4178
commit 235daff561
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 272 additions and 1271 deletions

View file

@ -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;

View file

@ -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 ?? '<NA>'},
error: ${error ?? '<NA>'},
stack: ${stack ?? '<NA>'},
}''';
}
@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;
}

View file

@ -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;
}

View file

@ -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<String, dynamic> toMap() {
return <String, dynamic>{'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<String, dynamic>);
@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<RemoteAsset> 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<RemoteAsset>? 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 ?? "<NA>"},
ownerId: $ownerId,
type: $type,
data: $data,
isSaved: $isSaved,
memoryAt: $memoryAt,
seenAt: ${seenAt ?? "<NA>"},
showAt: ${showAt ?? "<NA>"},
hideAt: ${hideAt ?? "<NA>"},
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<RemoteAsset> assets,
}) = _DriftMemory;
}

View file

@ -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<String, dynamic> toMap() {
return <String, dynamic>{
'id': id,
'birthDate': birthDate?.millisecondsSinceEpoch,
'isHidden': isHidden,
'name': name,
'thumbnailPath': thumbnailPath,
'updatedAt': updatedAt?.millisecondsSinceEpoch,
};
}
factory PersonDto.fromMap(Map<String, dynamic> 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<String, dynamic>);
@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

View file

@ -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<BaseAsset> 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<BaseAsset> 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;
}

View file

@ -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);

View file

@ -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<String, dynamic> toMap() {
return <String, dynamic>{
'id': id,
'email': email,
'name': name,
'inTimeline': inTimeline,
'profileImagePath': profileImagePath,
};
}
factory PartnerUserDto.fromMap(Map<String, dynamic> 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<String, dynamic>);
@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 {

View file

@ -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<String, Object?> toMap() {
final onboarding = <String, Object?>{};
onboarding["isOnboarded"] = isOnboarded;
return onboarding;
}
const factory Onboarding({required bool isOnboarded}) = _Onboarding;
factory Onboarding.fromMap(Map<String, Object?> 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<String, Object?> toMap() {
final preferences = <String, Object?>{};
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<String, Object?> map) {
return Preferences(
@ -127,75 +54,14 @@ class Preferences {
minimumFaces: (map["people"] as Map<String, Object?>?)?["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<String, Object?> toMap() {
final license = <String, Object?>{};
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<String, Object?> 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

View file

@ -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<String, dynamic> toMap() {
return <String, dynamic>{'url': url, 'status': status.toMap()};
}
const factory AuxilaryEndpoint({required String url, required AuxCheckStatus status}) = _AuxilaryEndpoint;
factory AuxilaryEndpoint.fromMap(Map<String, dynamic> 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<String, dynamic>);
}
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<String, dynamic> toMap() {
return <String, dynamic>{'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<String, dynamic> 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<String, dynamic>);
@override
String toString() => 'AuxCheckStatus(name: $name)';
}

View file

@ -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<BiometricType> 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<BiometricType>? 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<BiometricType> availableBiometrics, required bool canAuthenticate}) =
_BiometricStatus;
}

View file

@ -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<String, dynamic> toMap() {
final result = <String, dynamic>{};
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<String, dynamic> 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;
}

View file

@ -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<String, dynamic> toMap() {
return <String, dynamic>{'fileName': fileName, 'progress': progress, 'status': status.index};
}
factory DownloadInfo.fromMap(Map<String, dynamic> 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<String, dynamic>);
@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<String, DownloadInfo> taskProgress;
final bool showProgress;
const DownloadState({required this.downloadStatus, required this.taskProgress, required this.showProgress});
DownloadState copyWith({TaskStatus? downloadStatus, Map<String, DownloadInfo>? 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<String, DownloadInfo> taskProgress,
required bool showProgress,
}) = _DownloadState;
}

View file

@ -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<String, dynamic> toMap() {
return <String, dynamic>{'label': label, 'subtitle': subtitle, 'id': id};
}
factory SearchCuratedContent.fromMap(Map<String, dynamic> 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<String, dynamic>);
@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;
}

View file

@ -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<String, dynamic> toMap() {
return <String, dynamic>{'isNotInAlbum': isNotInAlbum, 'isArchive': isArchive, 'isFavorite': isFavorite};
}
factory SearchDisplayFilters.fromMap(Map<String, dynamic> 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<String, dynamic>);
@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 {

View file

@ -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,
),

View file

@ -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<DisplayOption, bool> 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),
),
);
}

View file

@ -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

View file

@ -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;
}

View file

@ -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<String, DriftUploadStatus> uploadItems;
final Map<String, double> 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<String, DriftUploadStatus>? uploadItems,
Map<String, double>? 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<String, DriftUploadStatus> uploadItems,
@Default({}) Map<String, double> 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<DriftBackupNotifier, DriftBackupState>((ref) {

View file

@ -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<RemoteAlbum> 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<RemoteAlbum>? albums}) {
return RemoteAlbumState(albums: albums ?? this.albums);
}
const factory RemoteAlbumState({required List<RemoteAlbum> 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<RemoteAlbumState> {

View file

@ -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<List<String>, SearchSuggestionArgs>((

View file

@ -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, MultiSelectState>(
MultiSelectNotifier.new,
dependencies: [timelineServiceProvider],
);
class MultiSelectState {
final Set<BaseAsset> selectedAssets;
final Set<BaseAsset> 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<BaseAsset> selectedAssets,
required Set<BaseAsset> 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<BaseAsset>? selectedAssets,
Set<BaseAsset>? 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<MultiSelectState> {

View file

@ -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<String, dynamic> toMap() {
final result = <String, dynamic>{};
result.addAll({'status': status.index});
result.addAll({'profileImagePath': profileImagePath});
return result;
}
factory UploadProfileImageState.fromMap(Map<String, dynamic> 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<UploadProfileImageState> {

View file

@ -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<String, dynamic> toMap() {
return <String, dynamic>{
@ -70,28 +68,10 @@ class UploadTaskMetadata {
);
}
String toJson() => json.encode(toMap());
factory UploadTaskMetadata.fromJson(String source) =>
UploadTaskMetadata.fromMap(json.decode(source) as Map<String, dynamic>);
@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)

View file

@ -21,7 +21,9 @@ class CameraPicker extends HookConsumerWidget {
final selectedMake = useState<String?>(filter?.make);
final selectedModel = useState<String?>(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(