Refactor: Implement multi-item inventory for Pad Tracker and dynamic navigation

This commit is contained in:
2026-01-02 18:10:50 -06:00
parent 56683f5407
commit 8772b56f36
44 changed files with 3515 additions and 781 deletions

View File

@@ -84,7 +84,7 @@ class CycleEntry extends HiveObject {
@HiveField(1)
DateTime date;
@HiveField(2)
@HiveField(2, defaultValue: false)
bool isPeriodDay;
@HiveField(3)
@@ -99,34 +99,34 @@ class CycleEntry extends HiveObject {
@HiveField(6)
int? crampIntensity; // 1-5
@HiveField(7)
@HiveField(7, defaultValue: false)
bool hasHeadache;
@HiveField(8)
@HiveField(8, defaultValue: false)
bool hasBloating;
@HiveField(9)
@HiveField(9, defaultValue: false)
bool hasBreastTenderness;
@HiveField(10)
@HiveField(10, defaultValue: false)
bool hasFatigue;
@HiveField(11)
@HiveField(11, defaultValue: false)
bool hasAcne;
@HiveField(22)
@HiveField(22, defaultValue: false)
bool hasLowerBackPain;
@HiveField(23)
@HiveField(23, defaultValue: false)
bool hasConstipation;
@HiveField(24)
@HiveField(24, defaultValue: false)
bool hasDiarrhea;
@HiveField(25)
int? stressLevel; // 1-5
@HiveField(26)
@HiveField(26, defaultValue: false)
bool hasInsomnia;
@HiveField(12)
@@ -147,10 +147,10 @@ class CycleEntry extends HiveObject {
@HiveField(17)
int? waterIntake; // glasses
@HiveField(18)
@HiveField(18, defaultValue: false)
bool hadExercise;
@HiveField(19)
@HiveField(19, defaultValue: false)
bool hadIntimacy; // For married users only
@HiveField(20)
@@ -338,7 +338,7 @@ extension FlowIntensityExtension on FlowIntensity {
case FlowIntensity.light:
return 'Light';
case FlowIntensity.medium:
return 'Medium';
return 'Regular';
case FlowIntensity.heavy:
return 'Heavy';
}

View File

@@ -19,21 +19,21 @@ class CycleEntryAdapter extends TypeAdapter<CycleEntry> {
return CycleEntry(
id: fields[0] as String,
date: fields[1] as DateTime,
isPeriodDay: fields[2] as bool,
isPeriodDay: fields[2] == null ? false : fields[2] as bool,
flowIntensity: fields[3] as FlowIntensity?,
mood: fields[4] as MoodLevel?,
energyLevel: fields[5] as int?,
crampIntensity: fields[6] as int?,
hasHeadache: fields[7] as bool,
hasBloating: fields[8] as bool,
hasBreastTenderness: fields[9] as bool,
hasFatigue: fields[10] as bool,
hasAcne: fields[11] as bool,
hasLowerBackPain: fields[22] as bool,
hasConstipation: fields[23] as bool,
hasDiarrhea: fields[24] as bool,
hasHeadache: fields[7] == null ? false : fields[7] as bool,
hasBloating: fields[8] == null ? false : fields[8] as bool,
hasBreastTenderness: fields[9] == null ? false : fields[9] as bool,
hasFatigue: fields[10] == null ? false : fields[10] as bool,
hasAcne: fields[11] == null ? false : fields[11] as bool,
hasLowerBackPain: fields[22] == null ? false : fields[22] as bool,
hasConstipation: fields[23] == null ? false : fields[23] as bool,
hasDiarrhea: fields[24] == null ? false : fields[24] as bool,
stressLevel: fields[25] as int?,
hasInsomnia: fields[26] as bool,
hasInsomnia: fields[26] == null ? false : fields[26] as bool,
basalBodyTemperature: fields[12] as double?,
cervicalMucus: fields[13] as CervicalMucusType?,
ovulationTestPositive: fields[14] as bool?,
@@ -41,8 +41,8 @@ class CycleEntryAdapter extends TypeAdapter<CycleEntry> {
cravings: (fields[27] as List?)?.cast<String>(),
sleepHours: fields[16] as int?,
waterIntake: fields[17] as int?,
hadExercise: fields[18] as bool,
hadIntimacy: fields[19] as bool,
hadExercise: fields[18] == null ? false : fields[18] as bool,
hadIntimacy: fields[19] == null ? false : fields[19] as bool,
intimacyProtected: fields[29] as bool?,
createdAt: fields[20] as DateTime,
updatedAt: fields[21] as DateTime,

View File

@@ -12,15 +12,15 @@ part 'scripture.g.dart'; // Hive generated adapter
/// Scripture model for daily verses and devotionals
@HiveType(typeId: 10) // Unique typeId for Scripture
class Scripture extends HiveObject {
@HiveField(0)
@HiveField(0, defaultValue: {})
final Map<BibleTranslation, String> verses;
@HiveField(1)
final String reference;
@HiveField(2)
final String? reflection;
@HiveField(3)
@HiveField(3, defaultValue: [])
final List<String> applicablePhases;
@HiveField(4)
@HiveField(4, defaultValue: [])
final List<String> applicableContexts;
Scripture({

View File

@@ -17,11 +17,15 @@ class ScriptureAdapter extends TypeAdapter<Scripture> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Scripture(
verses: (fields[0] as Map).cast<BibleTranslation, String>(),
verses: fields[0] == null
? {}
: (fields[0] as Map).cast<BibleTranslation, String>(),
reference: fields[1] as String,
reflection: fields[2] as String?,
applicablePhases: (fields[3] as List).cast<String>(),
applicableContexts: (fields[4] as List).cast<String>(),
applicablePhases:
fields[3] == null ? [] : (fields[3] as List).cast<String>(),
applicableContexts:
fields[4] == null ? [] : (fields[4] as List).cast<String>(),
);
}

View File

@@ -56,6 +56,86 @@ enum AppThemeMode {
dark,
}
@HiveType(typeId: 13)
enum PadType {
@HiveField(0)
pantyLiner,
@HiveField(1)
regular,
@HiveField(2)
super_pad,
@HiveField(3)
overnight,
@HiveField(4)
tampon_regular,
@HiveField(5)
tampon_super,
@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.super_pad:
return 'Super Pad';
case PadType.overnight:
return 'Overnight';
case PadType.tampon_regular:
return 'Tampon (Regular)';
case PadType.tampon_super:
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 {
@@ -65,28 +145,28 @@ class UserProfile extends HiveObject {
@HiveField(1)
String name;
@HiveField(2)
@HiveField(2, defaultValue: RelationshipStatus.single)
RelationshipStatus relationshipStatus;
@HiveField(3)
FertilityGoal? fertilityGoal;
@HiveField(4)
@HiveField(4, defaultValue: 28)
int averageCycleLength;
@HiveField(5)
@HiveField(5, defaultValue: 5)
int averagePeriodLength;
@HiveField(6)
DateTime? lastPeriodStartDate;
@HiveField(7)
DateTime? lastPadChangeTime;
@HiveField(8, defaultValue: true)
bool notificationsEnabled;
@HiveField(8)
String? devotionalTime; // HH:mm format
@HiveField(9)
@HiveField(9, defaultValue: false)
bool hasCompletedOnboarding;
@HiveField(10)
@@ -138,6 +218,44 @@ class UserProfile extends HiveObject {
@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;
UserProfile({
required this.id,
required this.name,
@@ -147,7 +265,6 @@ class UserProfile extends HiveObject {
this.averagePeriodLength = 5,
this.lastPeriodStartDate,
this.notificationsEnabled = true,
this.devotionalTime,
this.hasCompletedOnboarding = false,
required this.createdAt,
required this.updatedAt,
@@ -165,6 +282,19 @@ class UserProfile extends HiveObject {
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,
});
/// Check if user is married
@@ -199,7 +329,6 @@ class UserProfile extends HiveObject {
int? averagePeriodLength,
DateTime? lastPeriodStartDate,
bool? notificationsEnabled,
String? devotionalTime,
bool? hasCompletedOnboarding,
DateTime? createdAt,
DateTime? updatedAt,
@@ -217,6 +346,19 @@ class UserProfile extends HiveObject {
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,
}) {
return UserProfile(
id: id ?? this.id,
@@ -227,7 +369,6 @@ class UserProfile extends HiveObject {
averagePeriodLength: averagePeriodLength ?? this.averagePeriodLength,
lastPeriodStartDate: lastPeriodStartDate ?? this.lastPeriodStartDate,
notificationsEnabled: notificationsEnabled ?? this.notificationsEnabled,
devotionalTime: devotionalTime ?? this.devotionalTime,
hasCompletedOnboarding:
hasCompletedOnboarding ?? this.hasCompletedOnboarding,
createdAt: createdAt ?? this.createdAt,
@@ -246,6 +387,19 @@ class UserProfile extends HiveObject {
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,
);
}
}

View File

@@ -6,6 +6,49 @@ part of 'user_profile.dart';
// TypeAdapterGenerator
// **************************************************************************
class SupplyItemAdapter extends TypeAdapter<SupplyItem> {
@override
final int typeId = 12;
@override
SupplyItem read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return SupplyItem(
brand: fields[0] as String,
type: fields[1] as PadType,
absorbency: fields[2] as int,
count: fields[3] as int,
);
}
@override
void write(BinaryWriter writer, SupplyItem obj) {
writer
..writeByte(4)
..writeByte(0)
..write(obj.brand)
..writeByte(1)
..write(obj.type)
..writeByte(2)
..write(obj.absorbency)
..writeByte(3)
..write(obj.count);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is SupplyItemAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class UserProfileAdapter extends TypeAdapter<UserProfile> {
@override
final int typeId = 2;
@@ -19,14 +62,15 @@ class UserProfileAdapter extends TypeAdapter<UserProfile> {
return UserProfile(
id: fields[0] as String,
name: fields[1] as String,
relationshipStatus: fields[2] as RelationshipStatus,
relationshipStatus: fields[2] == null
? RelationshipStatus.single
: fields[2] as RelationshipStatus,
fertilityGoal: fields[3] as FertilityGoal?,
averageCycleLength: fields[4] as int,
averagePeriodLength: fields[5] as int,
averageCycleLength: fields[4] == null ? 28 : fields[4] as int,
averagePeriodLength: fields[5] == null ? 5 : fields[5] as int,
lastPeriodStartDate: fields[6] as DateTime?,
notificationsEnabled: fields[7] as bool,
devotionalTime: fields[8] as String?,
hasCompletedOnboarding: fields[9] as bool,
notificationsEnabled: fields[8] == null ? true : fields[8] as bool,
hasCompletedOnboarding: fields[9] == null ? false : fields[9] as bool,
createdAt: fields[10] as DateTime,
updatedAt: fields[11] as DateTime,
partnerName: fields[12] as String?,
@@ -46,13 +90,26 @@ class UserProfileAdapter extends TypeAdapter<UserProfile> {
shareEnergyLevels: fields[24] == null ? true : fields[24] as bool,
shareSleep: fields[25] == null ? true : fields[25] as bool,
shareIntimacy: fields[26] == null ? true : fields[26] as bool,
isPadTrackingEnabled: fields[27] == null ? false : fields[27] as bool,
typicalFlowIntensity: fields[28] as int?,
padBrand: fields[29] as String?,
padAbsorbency: fields[30] as int?,
padInventoryCount: fields[31] == null ? 0 : fields[31] as int,
lowInventoryThreshold: fields[32] == null ? 5 : fields[32] as int,
isAutoInventoryEnabled: fields[33] == null ? true : fields[33] as bool,
lastInventoryUpdate: fields[34] as DateTime?,
notifyPeriodEstimate: fields[35] == null ? true : fields[35] as bool,
notifyPeriodStart: fields[36] == null ? true : fields[36] as bool,
notifyLowSupply: fields[37] == null ? true : fields[37] as bool,
lastPadChangeTime: fields[7] as DateTime?,
padSupplies: (fields[38] as List?)?.cast<SupplyItem>(),
);
}
@override
void write(BinaryWriter writer, UserProfile obj) {
writer
..writeByte(26)
..writeByte(38)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -68,9 +125,9 @@ class UserProfileAdapter extends TypeAdapter<UserProfile> {
..writeByte(6)
..write(obj.lastPeriodStartDate)
..writeByte(7)
..write(obj.notificationsEnabled)
..write(obj.lastPadChangeTime)
..writeByte(8)
..write(obj.devotionalTime)
..write(obj.notificationsEnabled)
..writeByte(9)
..write(obj.hasCompletedOnboarding)
..writeByte(10)
@@ -104,7 +161,31 @@ class UserProfileAdapter extends TypeAdapter<UserProfile> {
..writeByte(25)
..write(obj.shareSleep)
..writeByte(26)
..write(obj.shareIntimacy);
..write(obj.shareIntimacy)
..writeByte(27)
..write(obj.isPadTrackingEnabled)
..writeByte(28)
..write(obj.typicalFlowIntensity)
..writeByte(29)
..write(obj.padBrand)
..writeByte(30)
..write(obj.padAbsorbency)
..writeByte(31)
..write(obj.padInventoryCount)
..writeByte(32)
..write(obj.lowInventoryThreshold)
..writeByte(33)
..write(obj.isAutoInventoryEnabled)
..writeByte(34)
..write(obj.lastInventoryUpdate)
..writeByte(38)
..write(obj.padSupplies)
..writeByte(35)
..write(obj.notifyPeriodEstimate)
..writeByte(36)
..write(obj.notifyPeriodStart)
..writeByte(37)
..write(obj.notifyLowSupply);
}
@override
@@ -314,6 +395,80 @@ class AppThemeModeAdapter extends TypeAdapter<AppThemeMode> {
typeId == other.typeId;
}
class PadTypeAdapter extends TypeAdapter<PadType> {
@override
final int typeId = 13;
@override
PadType read(BinaryReader reader) {
switch (reader.readByte()) {
case 0:
return PadType.pantyLiner;
case 1:
return PadType.regular;
case 2:
return PadType.super_pad;
case 3:
return PadType.overnight;
case 4:
return PadType.tampon_regular;
case 5:
return PadType.tampon_super;
case 6:
return PadType.menstrualCup;
case 7:
return PadType.menstrualDisc;
case 8:
return PadType.periodUnderwear;
default:
return PadType.pantyLiner;
}
}
@override
void write(BinaryWriter writer, PadType obj) {
switch (obj) {
case PadType.pantyLiner:
writer.writeByte(0);
break;
case PadType.regular:
writer.writeByte(1);
break;
case PadType.super_pad:
writer.writeByte(2);
break;
case PadType.overnight:
writer.writeByte(3);
break;
case PadType.tampon_regular:
writer.writeByte(4);
break;
case PadType.tampon_super:
writer.writeByte(5);
break;
case PadType.menstrualCup:
writer.writeByte(6);
break;
case PadType.menstrualDisc:
writer.writeByte(7);
break;
case PadType.periodUnderwear:
writer.writeByte(8);
break;
}
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is PadTypeAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class UserRoleAdapter extends TypeAdapter<UserRole> {
@override
final int typeId = 8;