@@ -40,6 +40,7 @@ from line in allLines.Skip(1).Take(allLines.Count() - 1)
4040 let temparry = line . Split ( ',' )
4141 let parse = int . TryParse ( temparry [ 1 ] , out parseScore )
4242 let isCompleted = temparry . Length > 3 && temparry [ 3 ] != null && temparry [ 3 ] == "True"
43+ let parsedLastModifiedDate = temparry . Length > 7 && DateTime . TryParse ( temparry [ 7 ] , out DateTime lmd ) ? lmd : DateTime . Now
4344 select new ItemGrid {
4445 Task = temparry [ 0 ] ,
4546 Score = parseScore ,
@@ -48,12 +49,16 @@ from line in allLines.Skip(1).Take(allLines.Count() - 1)
4849 Importance = temparry . Length > 4 && ! string . IsNullOrWhiteSpace ( temparry [ 4 ] ) ? temparry [ 4 ] : "Unknown" ,
4950 Urgency = temparry . Length > 5 && ! string . IsNullOrWhiteSpace ( temparry [ 5 ] ) ? temparry [ 5 ] : "Unknown" ,
5051 CreatedDate = temparry . Length > 6 && DateTime . TryParse ( temparry [ 6 ] , out DateTime cd ) ? cd : DateTime . Now ,
51- LastModifiedDate = temparry . Length > 7 && DateTime . TryParse ( temparry [ 7 ] , out DateTime lmd ) ? lmd : DateTime . Now ,
52+ LastModifiedDate = parsedLastModifiedDate ,
5253 ReminderTime = temparry . Length > 8 && DateTime . TryParse ( temparry [ 8 ] , out DateTime rt ) ? rt : ( DateTime ? ) null ,
5354 LongTermGoalId = temparry . Length > 9 && ! string . IsNullOrWhiteSpace ( temparry [ 9 ] ) ? temparry [ 9 ] : null ,
5455 OriginalScheduledDay = temparry . Length > 10 && int . TryParse ( temparry [ 10 ] , out int osd ) ? osd : 0 ,
5556 IsActiveInQuadrant = temparry . Length > 11 && bool . TryParse ( temparry [ 11 ] , out bool iaiq ) ? iaiq : true , // Default to true for backward compatibility
56- InactiveWarningCount = temparry . Length > 12 && int . TryParse ( temparry [ 12 ] , out int iwc ) ? iwc : 0 // New field
57+ InactiveWarningCount = temparry . Length > 12 && int . TryParse ( temparry [ 12 ] , out int iwc ) ? iwc : 0 ,
58+ LastProgressDate = temparry . Length > 13 && DateTime . TryParse ( temparry [ 13 ] , out DateTime lpd ) ? lpd : parsedLastModifiedDate ,
59+ LastInteractionDate = temparry . Length > 14 && DateTime . TryParse ( temparry [ 14 ] , out DateTime lid ) ? lid : parsedLastModifiedDate ,
60+ ReminderSnoozeUntil = temparry . Length > 15 && DateTime . TryParse ( temparry [ 15 ] , out DateTime rsu ) ? rsu : ( DateTime ? ) null ,
61+ LastReminderDate = temparry . Length > 16 && DateTime . TryParse ( temparry [ 16 ] , out DateTime lrd ) ? lrd : ( DateTime ? ) null
5762 } ;
5863 var result_list = new List < ItemGrid > ( ) ;
5964 try
@@ -71,12 +76,12 @@ from line in allLines.Skip(1).Take(allLines.Count() - 1)
7176 public static void WriteCsv ( IEnumerable < ItemGrid > items , string filepath )
7277 {
7378 var temparray = items . Select ( item =>
74- $ "{ item . Task } ,{ item . Score } ,{ item . Result } ,{ ( item . IsActive ? "False" : "True" ) } ,{ item . Importance ?? "Unknown" } ,{ item . Urgency ?? "Unknown" } ,{ item . CreatedDate : o} ,{ item . LastModifiedDate : o} ,{ item . ReminderTime ? . ToString ( "o" ) ?? "" } ,{ item . LongTermGoalId ?? "" } ,{ item . OriginalScheduledDay } ,{ item . IsActiveInQuadrant } ,{ item . InactiveWarningCount } " // Added InactiveWarningCount
79+ $ "{ item . Task } ,{ item . Score } ,{ item . Result } ,{ ( item . IsActive ? "False" : "True" ) } ,{ item . Importance ?? "Unknown" } ,{ item . Urgency ?? "Unknown" } ,{ item . CreatedDate : o} ,{ item . LastModifiedDate : o} ,{ item . ReminderTime ? . ToString ( "o" ) ?? "" } ,{ item . LongTermGoalId ?? "" } ,{ item . OriginalScheduledDay } ,{ item . IsActiveInQuadrant } ,{ item . InactiveWarningCount } , { item . LastProgressDate : o } , { item . LastInteractionDate : o } , { item . ReminderSnoozeUntil ? . ToString ( "o" ) ?? "" } , { item . LastReminderDate ? . ToString ( "o" ) ?? "" } "
7580 ) . ToArray ( ) ;
7681 var contents = new string [ temparray . Length + 2 ] ;
7782 Array . Copy ( temparray , 0 , contents , 1 , temparray . Length ) ;
7883 // Updated header
79- contents [ 0 ] = "task,score,result,is_completed,importance,urgency,createdDate,lastModifiedDate,reminderTime,longTermGoalId,originalScheduledDay,isActiveInQuadrant,inactiveWarningCount" ; // Added inactiveWarningCount header
84+ contents [ 0 ] = "task,score,result,is_completed,importance,urgency,createdDate,lastModifiedDate,reminderTime,longTermGoalId,originalScheduledDay,isActiveInQuadrant,inactiveWarningCount,lastProgressDate,lastInteractionDate,reminderSnoozeUntil,lastReminderDate" ;
8085 File . WriteAllLines ( filepath , contents ) ;
8186 }
8287
@@ -283,7 +288,11 @@ public class ItemGrid
283288 public string Urgency { set ; get ; } = "Unknown" ;
284289 public DateTime CreatedDate { set ; get ; } = DateTime . Now ;
285290 public DateTime LastModifiedDate { set ; get ; } = DateTime . Now ;
291+ public DateTime LastProgressDate { get ; set ; } = DateTime . Now ;
292+ public DateTime LastInteractionDate { get ; set ; } = DateTime . Now ;
286293 public DateTime ? ReminderTime { get ; set ; } = null ;
294+ public DateTime ? ReminderSnoozeUntil { get ; set ; } = null ;
295+ public DateTime ? LastReminderDate { get ; set ; } = null ;
287296 public string TaskType { get ; set ; }
288297 public DateTime ? CompletionTime { get ; set ; }
289298 public string CompletionStatus { get ; set ; }
@@ -318,6 +327,10 @@ public partial class MainWindow : Window
318327 private System . Windows . Threading . DispatcherTimer _reminderTimer ;
319328 private System . Windows . Threading . DispatcherTimer _draftBadgeTimer ;
320329 private TaskDraftManager _draftBadgeManager ;
330+ private const int DailyReminderLimit = 3 ;
331+ private static readonly TimeSpan ReminderCooldown = TimeSpan . FromMinutes ( 45 ) ;
332+ private int _remindersShownToday = 0 ;
333+ private DateTime _reminderCounterDate = DateTime . Today ;
321334
322335 private DatabaseService _databaseService ;
323336 private System . Windows . Threading . DispatcherTimer _syncTimer ;
@@ -765,33 +778,41 @@ private void ShowSimpleNotification(ItemGrid task, string message)
765778
766779 private async Task HandleTaskReminderResult ( ItemGrid task , TaskReminderResult result )
767780 {
781+ DateTime now = DateTime . Now ;
782+
768783 switch ( result )
769784 {
770785 case TaskReminderResult . Completed :
771786 task . IsActive = false ;
772- task . CompletionTime = DateTime . Now ;
787+ task . CompletionTime = now ;
773788 task . CompletionStatus = "Completed" ;
774- task . LastModifiedDate = DateTime . Now ;
789+ task . LastModifiedDate = now ;
790+ task . LastProgressDate = now ;
791+ task . LastInteractionDate = now ;
792+ task . InactiveWarningCount = 0 ;
793+ task . ReminderSnoozeUntil = null ;
775794 break ;
776795
777796 case TaskReminderResult . Updated :
778- task . LastModifiedDate = DateTime . Now ;
797+ task . LastModifiedDate = now ;
798+ task . LastProgressDate = now ;
799+ task . LastInteractionDate = now ;
779800 task . InactiveWarningCount = 0 ; // Reset warning count
801+ task . ReminderSnoozeUntil = null ;
780802 break ;
781803
782804 case TaskReminderResult . Decompose :
783805 await HandleTaskDecomposition ( task ) ;
784806 break ;
785807
786808 case TaskReminderResult . Snoozed :
787- // Snooze for 1 day
788- task . LastModifiedDate = DateTime . Now . AddDays ( - Properties . Settings . Default . FirstWarningAfterDays + 1 ) ;
809+ task . LastInteractionDate = now ;
810+ task . ReminderSnoozeUntil = now . AddHours ( 8 ) ;
789811 break ;
790812
791813 case TaskReminderResult . Dismissed :
792814 default :
793- // Do nothing, just update last interaction time
794- task . LastModifiedDate = DateTime . Now ;
815+ task . LastInteractionDate = now ;
795816 break ;
796817 }
797818
@@ -821,6 +842,10 @@ private async Task HandleTaskDecomposition(ItemGrid task)
821842 task . IsActive = false ;
822843 task . CompletionStatus = "Decomposed" ;
823844 task . LastModifiedDate = DateTime . Now ;
845+ task . LastProgressDate = DateTime . Now ;
846+ task . LastInteractionDate = DateTime . Now ;
847+ task . InactiveWarningCount = 0 ;
848+ task . ReminderSnoozeUntil = null ;
824849
825850 MessageBox . Show ( this , $ "任务已成功分解为 { subTaskStrings . Count } 个子任务并分配到相应象限。",
826851 "任务分解完成" , MessageBoxButton . OK , MessageBoxImage . Information ) ;
@@ -863,6 +888,8 @@ private Task AddSubTasksToQuadrants(Dictionary<string, int> taskQuadrantAssignme
863888 IsActive = true ,
864889 CreatedDate = DateTime . Now ,
865890 LastModifiedDate = DateTime . Now ,
891+ LastProgressDate = DateTime . Now ,
892+ LastInteractionDate = DateTime . Now ,
866893 LongTermGoalId = originalTask . LongTermGoalId , // Inherit from parent
867894 IsActiveInQuadrant = true ,
868895 InactiveWarningCount = 0
@@ -935,36 +962,59 @@ private async void TaskReminderTimer_Tick(object sender, EventArgs e)
935962
936963 private Task CheckForStaleTasksAndRemind ( )
937964 {
965+ DateTime now = DateTime . Now ;
966+ if ( _reminderCounterDate != now . Date )
967+ {
968+ _reminderCounterDate = now . Date ;
969+ _remindersShownToday = 0 ;
970+ }
971+
972+ if ( _remindersShownToday >= DailyReminderLimit )
973+ {
974+ return Task . CompletedTask ;
975+ }
976+
938977 DataGrid [ ] dataGrids = { task1 , task2 , task3 , task4 } ;
978+ ItemGrid candidate = null ;
979+ TimeSpan candidateInactive = TimeSpan . Zero ;
939980
940981 foreach ( var dataGrid in dataGrids )
941982 {
942983 if ( dataGrid . ItemsSource is List < ItemGrid > tasks )
943984 {
944985 foreach ( var task in tasks . Where ( t => t . IsActive && t . IsActiveInQuadrant ) )
945986 {
946- TimeSpan inactiveDuration = DateTime . Now - task . LastModifiedDate ;
987+ TimeSpan inactiveDuration = now - task . LastProgressDate ;
947988
948- // Check if task needs reminder
949- if ( ShouldShowReminder ( task , inactiveDuration ) )
989+ if ( ShouldShowReminder ( task , inactiveDuration , now ) && inactiveDuration > candidateInactive )
950990 {
951- ShowFriendlyReminder ( task , GetReminderMessage ( task , inactiveDuration ) ) ;
952-
953- // Update warning count and last modified date
954- task . InactiveWarningCount ++ ;
955- task . LastModifiedDate = DateTime . Now ;
956-
957- // Only show one reminder per check cycle
958- break ;
991+ candidate = task ;
992+ candidateInactive = inactiveDuration ;
959993 }
960994 }
961995 }
962996 }
997+
998+ if ( candidate != null )
999+ {
1000+ ShowFriendlyReminder ( candidate , GetReminderMessage ( candidate , candidateInactive ) ) ;
1001+ candidate . InactiveWarningCount ++ ;
1002+ candidate . LastInteractionDate = now ;
1003+ candidate . LastReminderDate = now ;
1004+ _remindersShownToday ++ ;
1005+ UpdateTaskInAllGrids ( candidate ) ;
1006+ }
9631007 return Task . CompletedTask ;
9641008 }
9651009
966- private bool ShouldShowReminder ( ItemGrid task , TimeSpan inactiveDuration )
1010+ private bool ShouldShowReminder ( ItemGrid task , TimeSpan inactiveDuration , DateTime now )
9671011 {
1012+ if ( task . ReminderSnoozeUntil . HasValue && task . ReminderSnoozeUntil . Value > now )
1013+ return false ;
1014+
1015+ if ( task . LastReminderDate . HasValue && ( now - task . LastReminderDate . Value ) < ReminderCooldown )
1016+ return false ;
1017+
9681018 // First warning after configured days
9691019 if ( inactiveDuration > FirstWarningAfter && task . InactiveWarningCount == 0 )
9701020 return true ;
@@ -974,10 +1024,12 @@ private bool ShouldShowReminder(ItemGrid task, TimeSpan inactiveDuration)
9741024 return true ;
9751025
9761026 // Subsequent warnings for very stale tasks
977- if ( inactiveDuration > StaleTaskThreshold && task . InactiveWarningCount >= 2 )
1027+ if ( inactiveDuration > StaleTaskThreshold && task . InactiveWarningCount >= 2 && task . InactiveWarningCount < MaxInactiveWarnings )
9781028 {
9791029 // Show reminder every few days for very stale tasks
980- var daysSinceLastWarning = ( DateTime . Now - task . LastModifiedDate ) . TotalDays ;
1030+ var daysSinceLastWarning = task . LastReminderDate . HasValue
1031+ ? ( now - task . LastReminderDate . Value ) . TotalDays
1032+ : ( now - task . LastInteractionDate ) . TotalDays ;
9811033 return daysSinceLastWarning >= 3 ; // Remind every 3 days for very stale tasks
9821034 }
9831035
@@ -1232,7 +1284,8 @@ private void ReminderTimer_Tick(object sender, EventArgs e)
12321284
12331285 // Mark reminder as shown by clearing it
12341286 task . ReminderTime = null ;
1235- task . LastModifiedDate = now ; // Update last modified date
1287+ task . LastInteractionDate = now ;
1288+ task . LastReminderDate = now ;
12361289 changesMadeInCurrentGrid = true ;
12371290 // changesMadeOverall = true;
12381291 }
@@ -1272,6 +1325,10 @@ private void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventA
12721325
12731326 item . Task = newDescription ; // Update the task description
12741327 item . LastModifiedDate = DateTime . Now ; // Update the last modified date
1328+ item . LastProgressDate = DateTime . Now ;
1329+ item . LastInteractionDate = DateTime . Now ;
1330+ item . InactiveWarningCount = 0 ;
1331+ item . ReminderSnoozeUntil = null ;
12751332
12761333 Console . WriteLine ( $ "Task edited in grid. Task ID (if available): [{ item . SourceTaskID } ], New Description: [{ newDescription } ]") ;
12771334
@@ -1686,6 +1743,10 @@ internal static bool ProcessTaskDrop(ItemGrid draggedItem, List<ItemGrid> source
16861743 sourceList . Remove ( draggedItem ) ;
16871744 targetList . Add ( draggedItem ) ;
16881745 draggedItem . LastModifiedDate = DateTime . Now ;
1746+ draggedItem . LastProgressDate = DateTime . Now ;
1747+ draggedItem . LastInteractionDate = DateTime . Now ;
1748+ draggedItem . InactiveWarningCount = 0 ;
1749+ draggedItem . ReminderSnoozeUntil = null ;
16891750
16901751 string newImportance = "Unknown" ;
16911752 string newUrgency = "Unknown" ;
@@ -1737,6 +1798,10 @@ internal static bool ProcessTaskReorder(ItemGrid draggedItem, List<ItemGrid> lis
17371798
17381799 list . Insert ( actualInsertionIndex , draggedItem ) ;
17391800 draggedItem . LastModifiedDate = DateTime . Now ;
1801+ draggedItem . LastProgressDate = DateTime . Now ;
1802+ draggedItem . LastInteractionDate = DateTime . Now ;
1803+ draggedItem . InactiveWarningCount = 0 ;
1804+ draggedItem . ReminderSnoozeUntil = null ;
17401805
17411806 // Update scores
17421807 for ( int i = 0 ; i < list . Count ; i ++ )
@@ -2189,6 +2254,8 @@ private async Task HandleNormalGoalCreation(SetLongTermGoalWindow goalDialog)
21892254 Result = string . Empty ,
21902255 CreatedDate = DateTime . Now ,
21912256 LastModifiedDate = DateTime . Now ,
2257+ LastProgressDate = DateTime . Now ,
2258+ LastInteractionDate = DateTime . Now ,
21922259 LongTermGoalId = newLongTermGoal . Id ,
21932260 OriginalScheduledDay = dayNumber ,
21942261 IsActiveInQuadrant = false
0 commit comments