/// App-owned user profile synced between local Drift storage and Postgres. class UserProfile { const UserProfile({ required this.firebaseUid, this.email, this.displayName, this.photoUrl, this.locale = 'en', this.timezone, this.onboardingCompleted = false, this.revision = 1, required this.updatedAt, this.lastSyncedAt, this.dirty = false, }); final String firebaseUid; final String? email; final String? displayName; final String? photoUrl; final String locale; final String? timezone; final bool onboardingCompleted; final int revision; final DateTime updatedAt; final DateTime? lastSyncedAt; final bool dirty; UserProfile copyWith({ String? email, String? displayName, String? photoUrl, String? locale, String? timezone, bool? onboardingCompleted, int? revision, DateTime? updatedAt, DateTime? lastSyncedAt, bool? dirty, bool clearLastSyncedAt = false, }) { return UserProfile( firebaseUid: firebaseUid, email: email ?? this.email, displayName: displayName ?? this.displayName, photoUrl: photoUrl ?? this.photoUrl, locale: locale ?? this.locale, timezone: timezone ?? this.timezone, onboardingCompleted: onboardingCompleted ?? this.onboardingCompleted, revision: revision ?? this.revision, updatedAt: updatedAt ?? this.updatedAt, lastSyncedAt: clearLastSyncedAt ? null : (lastSyncedAt ?? this.lastSyncedAt), dirty: dirty ?? this.dirty, ); } factory UserProfile.fromAuth({ required String firebaseUid, String? email, String? displayName, String? photoUrl, }) { return UserProfile( firebaseUid: firebaseUid, email: email, displayName: displayName, photoUrl: photoUrl, updatedAt: DateTime.now().toUtc(), dirty: true, ); } factory UserProfile.fromJson(Map json) { return UserProfile( firebaseUid: json['firebaseUid'] as String, email: json['email'] as String?, displayName: json['displayName'] as String?, photoUrl: json['photoUrl'] as String?, locale: json['locale'] as String? ?? 'en', timezone: json['timezone'] as String?, onboardingCompleted: json['onboardingCompleted'] as bool? ?? false, revision: (json['revision'] as num?)?.toInt() ?? 1, updatedAt: DateTime.parse(json['updatedAt'] as String).toUtc(), lastSyncedAt: json['lastSyncedAt'] == null ? null : DateTime.parse(json['lastSyncedAt'] as String).toUtc(), dirty: json['dirty'] as bool? ?? false, ); } Map toJson() { return { 'firebaseUid': firebaseUid, 'email': email, 'displayName': displayName, 'photoUrl': photoUrl, 'locale': locale, 'timezone': timezone, 'onboardingCompleted': onboardingCompleted, 'revision': revision, 'updatedAt': updatedAt.toUtc().toIso8601String(), if (lastSyncedAt != null) 'lastSyncedAt': lastSyncedAt!.toUtc().toIso8601String(), 'dirty': dirty, }; } /// Payload sent to the API (server assigns authoritative revision on conflict). Map toApiJson() { return { 'email': email, 'displayName': displayName, 'photoUrl': photoUrl, 'locale': locale, 'timezone': timezone, 'onboardingCompleted': onboardingCompleted, 'revision': revision, 'updatedAt': updatedAt.toUtc().toIso8601String(), }; } }