Files
Tracker/lib/models/user_profile.dart
Sterlen 1c2c56e9e2 feat: Add auto-sync, fix partner linking UI, update sharing settings
- Add 10-second periodic auto-sync to CycleEntriesNotifier
- Fix husband_devotional_screen: use partnerId for isConnected check, navigate to SharingSettingsScreen instead of legacy mock dialog
- Remove obsolete _showConnectDialog method and mock data import
- Update husband_settings_screen: show 'Partner Settings' with linked partner name when connected
- Add SharingSettingsScreen: Pad Supplies toggle (disabled when pad tracking off), Intimacy always enabled
- Add CORS OPTIONS handler to backend server
- Add _ensureServerRegistration for reliable partner linking
- Add copy button to Invite Partner dialog
- Dynamic base URL for web (uses window.location.hostname)
2026-01-09 17:20:49 -06:00

554 lines
14 KiB
Dart

import 'package:hive/hive.dart';
import 'teaching_plan.dart';
part 'user_profile.g.dart';
/// User's relationship status
@HiveType(typeId: 0)
enum RelationshipStatus {
@HiveField(0)
single,
@HiveField(1)
engaged,
@HiveField(2)
married,
}
/// Fertility tracking goal for married users
@HiveType(typeId: 1)
enum FertilityGoal {
@HiveField(0)
tryingToConceive, // TTC
@HiveField(1)
tryingToAvoid, // TTA - NFP
@HiveField(2)
justTracking,
}
@HiveType(typeId: 9)
enum BibleTranslation {
@HiveField(0)
esv,
@HiveField(1)
niv,
@HiveField(2)
nkjv,
@HiveField(3)
nlt,
@HiveField(4)
nasb,
@HiveField(5)
kjv,
@HiveField(6)
msg,
}
@HiveType(typeId: 11)
enum AppThemeMode {
@HiveField(0)
system,
@HiveField(1)
light,
@HiveField(2)
dark,
}
@HiveType(typeId: 13)
enum PadType {
@HiveField(0)
pantyLiner,
@HiveField(1)
regular,
@HiveField(2)
superPad,
@HiveField(3)
overnight,
@HiveField(4)
tamponRegular,
@HiveField(5)
tamponSuper,
@HiveField(6)
menstrualCup,
@HiveField(7)
menstrualDisc,
@HiveField(8)
periodUnderwear,
}
extension PadTypeExtension on PadType {
String get label {
switch (this) {
case PadType.pantyLiner:
return 'Liner';
case PadType.regular:
return 'Regular Pad';
case PadType.superPad:
return 'Super Pad';
case PadType.overnight:
return 'Overnight';
case PadType.tamponRegular:
return 'Tampon (Regular)';
case PadType.tamponSuper:
return 'Tampon (Super)';
case PadType.menstrualCup:
return 'Cup';
case PadType.menstrualDisc:
return 'Disc';
case PadType.periodUnderwear:
return 'Period Underwear';
}
}
}
@HiveType(typeId: 12)
class SupplyItem extends HiveObject {
@HiveField(0)
String brand;
@HiveField(1)
PadType type;
@HiveField(2)
int absorbency; // 1-5
@HiveField(3)
int count;
SupplyItem({
required this.brand,
required this.type,
required this.absorbency,
required this.count,
});
SupplyItem copyWith({
String? brand,
PadType? type,
int? absorbency,
int? count,
}) {
return SupplyItem(
brand: brand ?? this.brand,
type: type ?? this.type,
absorbency: absorbency ?? this.absorbency,
count: count ?? this.count,
);
}
}
/// User profile model
@HiveType(typeId: 2)
class UserProfile extends HiveObject {
@HiveField(0)
String id;
@HiveField(1)
String name;
@HiveField(2, defaultValue: RelationshipStatus.single)
RelationshipStatus relationshipStatus;
@HiveField(3)
FertilityGoal? fertilityGoal;
@HiveField(4, defaultValue: 28)
int averageCycleLength;
@HiveField(5, defaultValue: 5)
int averagePeriodLength;
@HiveField(6)
DateTime? lastPeriodStartDate;
@HiveField(7)
DateTime? lastPadChangeTime;
@HiveField(8, defaultValue: true)
bool notificationsEnabled;
@HiveField(9, defaultValue: false)
bool hasCompletedOnboarding;
@HiveField(10)
DateTime createdAt;
@HiveField(11)
DateTime updatedAt;
@HiveField(12)
String? partnerName; // For married users
@HiveField(14, defaultValue: UserRole.wife)
UserRole role;
@HiveField(15, defaultValue: false)
bool isIrregularCycle;
@HiveField(41, defaultValue: 21)
int minCycleLength;
@HiveField(42, defaultValue: 40)
int maxCycleLength;
@HiveField(16, defaultValue: BibleTranslation.esv)
BibleTranslation bibleTranslation;
@HiveField(17)
List<String>? favoriteFoods;
@HiveField(18, defaultValue: false)
bool isDataShared;
@HiveField(19, defaultValue: AppThemeMode.system)
AppThemeMode themeMode;
@HiveField(20, defaultValue: '0xFFA8C5A8')
String accentColor;
// Sharing settings
@HiveField(21, defaultValue: true)
bool shareMoods;
@HiveField(22, defaultValue: true)
bool shareSymptoms;
@HiveField(23, defaultValue: true)
bool shareCravings;
@HiveField(24, defaultValue: true)
bool shareEnergyLevels;
@HiveField(25, defaultValue: true)
bool shareSleep;
@HiveField(26, defaultValue: true)
bool shareIntimacy;
// Pad Tracking
@HiveField(27, defaultValue: false)
bool isPadTrackingEnabled;
@HiveField(28)
int? typicalFlowIntensity; // 1-5
@HiveField(29)
String? padBrand;
@HiveField(30)
int? padAbsorbency; // 1-5 scale
@HiveField(31, defaultValue: 0)
int padInventoryCount;
@HiveField(32, defaultValue: 5)
int lowInventoryThreshold;
@HiveField(33, defaultValue: true)
bool isAutoInventoryEnabled;
@HiveField(34)
DateTime? lastInventoryUpdate;
@HiveField(38)
List<SupplyItem>? padSupplies;
// Granular Notification Settings
@HiveField(35, defaultValue: true)
bool notifyPeriodEstimate;
@HiveField(36, defaultValue: true)
bool notifyPeriodStart;
@HiveField(37, defaultValue: true)
bool notifyLowSupply;
@HiveField(39, defaultValue: true)
bool showPadTimerMinutes;
@HiveField(40, defaultValue: false)
bool showPadTimerSeconds;
@HiveField(43, defaultValue: false)
bool notifyPad1Hour;
@HiveField(44, defaultValue: false)
bool notifyPad2Hours;
@HiveField(45)
String? privacyPin;
@HiveField(46, defaultValue: false)
bool isBioProtected;
@HiveField(47, defaultValue: false)
bool isHistoryProtected;
@HiveField(48, defaultValue: false)
bool notifyPad30Mins;
@HiveField(49, defaultValue: true)
bool notifyPadNow;
@HiveField(50, defaultValue: false)
bool isLogProtected;
@HiveField(51, defaultValue: false)
bool isCalendarProtected;
@HiveField(52, defaultValue: false)
bool isSuppliesProtected;
@HiveField(53)
List<TeachingPlan>? teachingPlans;
// Husband-specific theme settings
@HiveField(54, defaultValue: AppThemeMode.system)
AppThemeMode husbandThemeMode;
@HiveField(55, defaultValue: '0xFF1A3A5C')
String husbandAccentColor;
// Whether to use example/demo data (for husband not connected to wife)
@HiveField(56, defaultValue: false)
bool useExampleData;
@HiveField(57)
String? partnerId; // ID of the partner to sync with
UserProfile({
required this.id,
required this.name,
this.relationshipStatus = RelationshipStatus.single,
this.fertilityGoal,
this.averageCycleLength = 28,
this.averagePeriodLength = 5,
this.lastPeriodStartDate,
this.notificationsEnabled = true,
this.hasCompletedOnboarding = false,
required this.createdAt,
required this.updatedAt,
this.partnerName,
this.role = UserRole.wife,
this.isIrregularCycle = false,
this.minCycleLength = 21,
this.maxCycleLength = 40,
this.bibleTranslation = BibleTranslation.esv,
this.favoriteFoods,
this.isDataShared = false,
this.themeMode = AppThemeMode.system,
this.accentColor = '0xFFA8C5A8',
this.shareMoods = true,
this.shareSymptoms = true,
this.shareCravings = true,
this.shareEnergyLevels = true,
this.shareSleep = true,
this.shareIntimacy = true,
this.isPadTrackingEnabled = false,
this.typicalFlowIntensity,
this.padBrand,
this.padAbsorbency,
this.padInventoryCount = 0,
this.lowInventoryThreshold = 5,
this.isAutoInventoryEnabled = true,
this.lastInventoryUpdate,
this.notifyPeriodEstimate = true,
this.notifyPeriodStart = true,
this.notifyLowSupply = true,
this.lastPadChangeTime,
this.padSupplies,
this.showPadTimerMinutes = true,
this.showPadTimerSeconds = false,
this.notifyPad1Hour = false,
this.notifyPad2Hours = false,
this.privacyPin,
this.isBioProtected = false,
this.isHistoryProtected = false,
this.notifyPad30Mins = false,
this.notifyPadNow = true,
this.isLogProtected = false,
this.isCalendarProtected = false,
this.isSuppliesProtected = false,
this.teachingPlans,
this.husbandThemeMode = AppThemeMode.system,
this.husbandAccentColor = '0xFF1A3A5C',
this.useExampleData = false,
this.partnerId,
});
/// Check if user is married
bool get isMarried => relationshipStatus == RelationshipStatus.married;
/// Check if user is trying to conceive
bool get isTTC => fertilityGoal == FertilityGoal.tryingToConceive;
/// Check if user is practicing NFP
bool get isNFP => fertilityGoal == FertilityGoal.tryingToAvoid;
/// Check if user is husband
bool get isHusband => role == UserRole.husband;
/// Should show fertility content
bool get showFertilityContent =>
!isHusband &&
isMarried &&
fertilityGoal != FertilityGoal.justTracking &&
fertilityGoal != null;
/// Should show intimacy recommendations
bool get showIntimacyContent => isMarried;
/// Copy with updated fields
UserProfile copyWith({
String? id,
String? name,
RelationshipStatus? relationshipStatus,
FertilityGoal? fertilityGoal,
int? averageCycleLength,
int? averagePeriodLength,
DateTime? lastPeriodStartDate,
bool? notificationsEnabled,
bool? hasCompletedOnboarding,
DateTime? createdAt,
DateTime? updatedAt,
String? partnerName,
UserRole? role,
bool? isIrregularCycle,
int? minCycleLength,
int? maxCycleLength,
BibleTranslation? bibleTranslation,
List<String>? favoriteFoods,
bool? isDataShared,
AppThemeMode? themeMode,
String? accentColor,
bool? shareMoods,
bool? shareSymptoms,
bool? shareCravings,
bool? shareEnergyLevels,
bool? shareSleep,
bool? shareIntimacy,
bool? isPadTrackingEnabled,
int? typicalFlowIntensity,
String? padBrand,
int? padAbsorbency,
int? padInventoryCount,
int? lowInventoryThreshold,
bool? isAutoInventoryEnabled,
DateTime? lastInventoryUpdate,
bool? notifyPeriodEstimate,
bool? notifyPeriodStart,
bool? notifyLowSupply,
DateTime? lastPadChangeTime,
List<SupplyItem>? padSupplies,
bool? showPadTimerMinutes,
bool? showPadTimerSeconds,
bool? notifyPad1Hour,
bool? notifyPad2Hours,
String? privacyPin,
bool? isBioProtected,
bool? isHistoryProtected,
bool? notifyPad30Mins,
bool? notifyPadNow,
bool? isLogProtected,
bool? isCalendarProtected,
bool? isSuppliesProtected,
List<TeachingPlan>? teachingPlans,
AppThemeMode? husbandThemeMode,
String? husbandAccentColor,
bool? useExampleData,
String? partnerId,
}) {
return UserProfile(
id: id ?? this.id,
name: name ?? this.name,
relationshipStatus: relationshipStatus ?? this.relationshipStatus,
fertilityGoal: fertilityGoal ?? this.fertilityGoal,
averageCycleLength: averageCycleLength ?? this.averageCycleLength,
averagePeriodLength: averagePeriodLength ?? this.averagePeriodLength,
lastPeriodStartDate: lastPeriodStartDate ?? this.lastPeriodStartDate,
notificationsEnabled: notificationsEnabled ?? this.notificationsEnabled,
hasCompletedOnboarding:
hasCompletedOnboarding ?? this.hasCompletedOnboarding,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? DateTime.now(),
partnerName: partnerName ?? this.partnerName,
role: role ?? this.role,
isIrregularCycle: isIrregularCycle ?? this.isIrregularCycle,
minCycleLength: minCycleLength ?? this.minCycleLength,
maxCycleLength: maxCycleLength ?? this.maxCycleLength,
bibleTranslation: bibleTranslation ?? this.bibleTranslation,
favoriteFoods: favoriteFoods ?? this.favoriteFoods,
isDataShared: isDataShared ?? this.isDataShared,
themeMode: themeMode ?? this.themeMode,
accentColor: accentColor ?? this.accentColor,
shareMoods: shareMoods ?? this.shareMoods,
shareSymptoms: shareSymptoms ?? this.shareSymptoms,
shareCravings: shareCravings ?? this.shareCravings,
shareEnergyLevels: shareEnergyLevels ?? this.shareEnergyLevels,
shareSleep: shareSleep ?? this.shareSleep,
shareIntimacy: shareIntimacy ?? this.shareIntimacy,
isPadTrackingEnabled: isPadTrackingEnabled ?? this.isPadTrackingEnabled,
typicalFlowIntensity: typicalFlowIntensity ?? this.typicalFlowIntensity,
padBrand: padBrand ?? this.padBrand,
padAbsorbency: padAbsorbency ?? this.padAbsorbency,
padInventoryCount: padInventoryCount ?? this.padInventoryCount,
lowInventoryThreshold:
lowInventoryThreshold ?? this.lowInventoryThreshold,
isAutoInventoryEnabled:
isAutoInventoryEnabled ?? this.isAutoInventoryEnabled,
lastInventoryUpdate: lastInventoryUpdate ?? this.lastInventoryUpdate,
notifyPeriodEstimate: notifyPeriodEstimate ?? this.notifyPeriodEstimate,
notifyPeriodStart: notifyPeriodStart ?? this.notifyPeriodStart,
notifyLowSupply: notifyLowSupply ?? this.notifyLowSupply,
lastPadChangeTime: lastPadChangeTime ?? this.lastPadChangeTime,
padSupplies: padSupplies ?? this.padSupplies,
showPadTimerMinutes: showPadTimerMinutes ?? this.showPadTimerMinutes,
showPadTimerSeconds: showPadTimerSeconds ?? this.showPadTimerSeconds,
notifyPad1Hour: notifyPad1Hour ?? this.notifyPad1Hour,
notifyPad2Hours: notifyPad2Hours ?? this.notifyPad2Hours,
privacyPin: privacyPin ?? this.privacyPin,
isBioProtected: isBioProtected ?? this.isBioProtected,
isHistoryProtected: isHistoryProtected ?? this.isHistoryProtected,
notifyPad30Mins: notifyPad30Mins ?? this.notifyPad30Mins,
notifyPadNow: notifyPadNow ?? this.notifyPadNow,
isLogProtected: isLogProtected ?? this.isLogProtected,
isCalendarProtected: isCalendarProtected ?? this.isCalendarProtected,
isSuppliesProtected: isSuppliesProtected ?? this.isSuppliesProtected,
teachingPlans: teachingPlans ?? this.teachingPlans,
husbandThemeMode: husbandThemeMode ?? this.husbandThemeMode,
husbandAccentColor: husbandAccentColor ?? this.husbandAccentColor,
useExampleData: useExampleData ?? this.useExampleData,
partnerId: partnerId ?? this.partnerId,
);
}
}
extension BibleTranslationExtension on BibleTranslation {
String get label {
switch (this) {
case BibleTranslation.esv:
return 'ESV';
case BibleTranslation.niv:
return 'NIV';
case BibleTranslation.nkjv:
return 'NKJV';
case BibleTranslation.nlt:
return 'NLT';
case BibleTranslation.nasb:
return 'NASB';
case BibleTranslation.kjv:
return 'KJV';
case BibleTranslation.msg:
return 'MSG';
}
}
}
@HiveType(typeId: 8)
enum UserRole {
@HiveField(0)
wife,
@HiveField(1)
husband,
}