Refine: Add real-time countdown timer and display settings to Pad Tracker
This commit is contained in:
@@ -256,6 +256,12 @@ class UserProfile extends HiveObject {
|
||||
@HiveField(37, defaultValue: true)
|
||||
bool notifyLowSupply;
|
||||
|
||||
@HiveField(39, defaultValue: true)
|
||||
bool showPadTimerMinutes;
|
||||
|
||||
@HiveField(40, defaultValue: false)
|
||||
bool showPadTimerSeconds;
|
||||
|
||||
UserProfile({
|
||||
required this.id,
|
||||
required this.name,
|
||||
@@ -295,6 +301,8 @@ class UserProfile extends HiveObject {
|
||||
this.notifyLowSupply = true,
|
||||
this.lastPadChangeTime,
|
||||
this.padSupplies,
|
||||
this.showPadTimerMinutes = true,
|
||||
this.showPadTimerSeconds = false,
|
||||
});
|
||||
|
||||
/// Check if user is married
|
||||
@@ -359,6 +367,8 @@ class UserProfile extends HiveObject {
|
||||
bool? notifyLowSupply,
|
||||
DateTime? lastPadChangeTime,
|
||||
List<SupplyItem>? padSupplies,
|
||||
bool? showPadTimerMinutes,
|
||||
bool? showPadTimerSeconds,
|
||||
}) {
|
||||
return UserProfile(
|
||||
id: id ?? this.id,
|
||||
@@ -400,6 +410,8 @@ class UserProfile extends HiveObject {
|
||||
notifyLowSupply: notifyLowSupply ?? this.notifyLowSupply,
|
||||
lastPadChangeTime: lastPadChangeTime ?? this.lastPadChangeTime,
|
||||
padSupplies: padSupplies ?? this.padSupplies,
|
||||
showPadTimerMinutes: showPadTimerMinutes ?? this.showPadTimerMinutes,
|
||||
showPadTimerSeconds: showPadTimerSeconds ?? this.showPadTimerSeconds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,13 +103,15 @@ class UserProfileAdapter extends TypeAdapter<UserProfile> {
|
||||
notifyLowSupply: fields[37] == null ? true : fields[37] as bool,
|
||||
lastPadChangeTime: fields[7] as DateTime?,
|
||||
padSupplies: (fields[38] as List?)?.cast<SupplyItem>(),
|
||||
showPadTimerMinutes: fields[39] == null ? true : fields[39] as bool,
|
||||
showPadTimerSeconds: fields[40] == null ? false : fields[40] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, UserProfile obj) {
|
||||
writer
|
||||
..writeByte(38)
|
||||
..writeByte(40)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
@@ -185,7 +187,11 @@ class UserProfileAdapter extends TypeAdapter<UserProfile> {
|
||||
..writeByte(36)
|
||||
..write(obj.notifyPeriodStart)
|
||||
..writeByte(37)
|
||||
..write(obj.notifyLowSupply);
|
||||
..write(obj.notifyLowSupply)
|
||||
..writeByte(39)
|
||||
..write(obj.showPadTimerMinutes)
|
||||
..writeByte(40)
|
||||
..write(obj.showPadTimerSeconds);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -38,7 +38,7 @@ class _PadTrackerScreenState extends ConsumerState<PadTrackerScreen> {
|
||||
}
|
||||
|
||||
void _startTimer() {
|
||||
_timer = Timer.periodic(const Duration(minutes: 1), (timer) {
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_updateTimeSinceChange();
|
||||
@@ -342,17 +342,19 @@ class _PadTrackerScreenState extends ConsumerState<PadTrackerScreen> {
|
||||
const SizedBox(height: 8),
|
||||
if (_timeSinceLastChange != Duration.zero) ...[
|
||||
Text(
|
||||
isOverdue
|
||||
? '${(-remainingHours).toString()}h overdue'
|
||||
: '${remainingHours}h ${((_recommendedHours * 60) - _timeSinceLastChange.inMinutes) % 60}m',
|
||||
_formatRemainingTime(
|
||||
Duration(hours: _recommendedHours) - _timeSinceLastChange,
|
||||
user!
|
||||
),
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isOverdue ? AppColors.rose : AppColors.navyBlue,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
'Last changed: ${_formatDuration(_timeSinceLastChange)} ago',
|
||||
'Last changed: ${_formatDuration(_timeSinceLastChange, user)} ago',
|
||||
style: GoogleFonts.outfit(fontSize: 12, color: AppColors.warmGray),
|
||||
),
|
||||
] else ...[
|
||||
@@ -468,9 +470,46 @@ class _PadTrackerScreenState extends ConsumerState<PadTrackerScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDuration(Duration d) {
|
||||
if (d.inHours > 0) return '${d.inHours}h ${d.inMinutes % 60}m';
|
||||
return '${d.inMinutes}m';
|
||||
String _formatDuration(Duration d, UserProfile user) {
|
||||
final hours = d.inHours;
|
||||
final minutes = d.inMinutes % 60;
|
||||
final seconds = d.inSeconds % 60;
|
||||
|
||||
List<String> parts = [];
|
||||
if (hours > 0) parts.add('${hours}h');
|
||||
if (user.showPadTimerMinutes) parts.add('${minutes}m');
|
||||
if (user.showPadTimerSeconds) parts.add('${seconds}s');
|
||||
|
||||
if (parts.isEmpty) {
|
||||
if (hours == 0 && minutes == 0 && seconds == 0) return 'Just now';
|
||||
return '${d.inMinutes}m'; // Fallback
|
||||
}
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
String _formatRemainingTime(Duration remaining, UserProfile user) {
|
||||
final isOverdue = remaining.isNegative;
|
||||
final absRemaining = remaining.abs();
|
||||
|
||||
final hours = absRemaining.inHours;
|
||||
final minutes = absRemaining.inMinutes % 60;
|
||||
final seconds = absRemaining.inSeconds % 60;
|
||||
|
||||
List<String> parts = [];
|
||||
if (hours > 0) parts.add('${hours}h');
|
||||
if (user.showPadTimerMinutes) {
|
||||
parts.add('${minutes}m');
|
||||
}
|
||||
if (user.showPadTimerSeconds) {
|
||||
parts.add('${seconds}s');
|
||||
}
|
||||
|
||||
if (parts.isEmpty) {
|
||||
return isOverdue ? 'Overdue' : 'Change Now';
|
||||
}
|
||||
|
||||
String timeStr = parts.join(' ');
|
||||
return isOverdue ? '$timeStr overdue' : timeStr;
|
||||
}
|
||||
|
||||
Widget _buildSectionHeader(String title) {
|
||||
|
||||
@@ -26,6 +26,8 @@ class _SuppliesSettingsScreenState extends ConsumerState<SuppliesSettingsScreen>
|
||||
int _padInventoryCount = 0;
|
||||
int _lowInventoryThreshold = 5;
|
||||
bool _isAutoInventoryEnabled = true;
|
||||
bool _showPadTimerMinutes = true;
|
||||
bool _showPadTimerSeconds = false;
|
||||
final TextEditingController _brandController = TextEditingController();
|
||||
|
||||
@override
|
||||
@@ -40,6 +42,8 @@ class _SuppliesSettingsScreenState extends ConsumerState<SuppliesSettingsScreen>
|
||||
_lowInventoryThreshold = user.lowInventoryThreshold;
|
||||
_isAutoInventoryEnabled = user.isAutoInventoryEnabled;
|
||||
_brandController.text = user.padBrand ?? '';
|
||||
_showPadTimerMinutes = user.showPadTimerMinutes;
|
||||
_showPadTimerSeconds = user.showPadTimerSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +61,8 @@ class _SuppliesSettingsScreenState extends ConsumerState<SuppliesSettingsScreen>
|
||||
typicalFlowIntensity: _typicalFlow,
|
||||
isAutoInventoryEnabled: _isAutoInventoryEnabled,
|
||||
padBrand: _brandController.text.trim().isEmpty ? null : _brandController.text.trim(),
|
||||
showPadTimerMinutes: _showPadTimerMinutes,
|
||||
showPadTimerSeconds: _showPadTimerSeconds,
|
||||
);
|
||||
|
||||
await ref.read(userProfileProvider.notifier).updateProfile(updatedProfile);
|
||||
@@ -173,6 +179,40 @@ class _SuppliesSettingsScreenState extends ConsumerState<SuppliesSettingsScreen>
|
||||
onChanged: (val) => setState(() => _isAutoInventoryEnabled = val),
|
||||
activeColor: AppColors.menstrualPhase,
|
||||
),
|
||||
|
||||
const Divider(height: 32),
|
||||
|
||||
Text(
|
||||
'Timer Display Settings',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
'Show Minutes',
|
||||
style: GoogleFonts.outfit(fontWeight: FontWeight.w500, color: AppColors.charcoal),
|
||||
),
|
||||
value: _showPadTimerMinutes,
|
||||
onChanged: (val) => setState(() => _showPadTimerMinutes = val),
|
||||
activeColor: AppColors.menstrualPhase,
|
||||
),
|
||||
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
'Show Seconds',
|
||||
style: GoogleFonts.outfit(fontWeight: FontWeight.w500, color: AppColors.charcoal),
|
||||
),
|
||||
value: _showPadTimerSeconds,
|
||||
onChanged: (val) => setState(() => _showPadTimerSeconds = val),
|
||||
activeColor: AppColors.menstrualPhase,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user