From 3d199faf04afed0f72ee9516f5af4ae92b98c264 Mon Sep 17 00:00:00 2001 From: Kate Date: Tue, 17 Mar 2026 00:40:48 +0300 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=20"La?= =?UTF-8?q?st=20Updated"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Unlimotion.Domain/TaskItem.cs | 1 + src/Unlimotion.Interface/ReceiveTaskItem.cs | 1 + src/Unlimotion.Interface/TaskItemHubMold.cs | 1 + .../molds/Tasks/TaskItemMold.cs | Bin 5706 -> 5914 bytes .../TaskTreeManager.cs | 17 +++ src/Unlimotion.Test/InMemoryStorage.cs | 1 + src/Unlimotion.ViewModel/FileDbWatcher.cs | 5 +- .../MainWindowViewModel.cs | 112 +++++++++++++++++- src/Unlimotion.ViewModel/SortDefinition.cs | 19 +++ src/Unlimotion.ViewModel/TaskItemViewModel.cs | 3 + src/Unlimotion/Views/MainControl.axaml | 50 +++++++- 11 files changed, 202 insertions(+), 8 deletions(-) diff --git a/src/Unlimotion.Domain/TaskItem.cs b/src/Unlimotion.Domain/TaskItem.cs index 948248b..060b0e3 100644 --- a/src/Unlimotion.Domain/TaskItem.cs +++ b/src/Unlimotion.Domain/TaskItem.cs @@ -12,6 +12,7 @@ public record TaskItem public bool? IsCompleted { get; set; } = false; public bool IsCanBeCompleted { get; set; } = true; public DateTimeOffset CreatedDateTime { get; set; } = DateTimeOffset.UtcNow; + public DateTimeOffset? UpdatedDateTime { get; set; } public DateTimeOffset? UnlockedDateTime { get; set; } public DateTimeOffset? CompletedDateTime { get; set; } public DateTimeOffset? ArchiveDateTime { get; set; } diff --git a/src/Unlimotion.Interface/ReceiveTaskItem.cs b/src/Unlimotion.Interface/ReceiveTaskItem.cs index 3bf2aaa..3265b4e 100644 --- a/src/Unlimotion.Interface/ReceiveTaskItem.cs +++ b/src/Unlimotion.Interface/ReceiveTaskItem.cs @@ -19,6 +19,7 @@ public class ReceiveTaskItem : IClientMethod public bool? IsCompleted { get; set; } = false; public bool IsCanBeCompleted { get; set; } = false; public DateTimeOffset CreatedDateTime { get; set; } + public DateTimeOffset? UpdatedDateTime { get; set; } public DateTimeOffset? UnlockedDateTime { get; set; } public DateTimeOffset? CompletedDateTime { get; set; } public DateTimeOffset? ArchiveDateTime { get; set; } diff --git a/src/Unlimotion.Interface/TaskItemHubMold.cs b/src/Unlimotion.Interface/TaskItemHubMold.cs index aa499d2..853eafa 100644 --- a/src/Unlimotion.Interface/TaskItemHubMold.cs +++ b/src/Unlimotion.Interface/TaskItemHubMold.cs @@ -9,6 +9,7 @@ public class TaskItemHubMold public string Title { get; set; } public string Description { get; set; } public bool? IsCompleted { get; set; } = false; + public DateTimeOffset? UpdatedDateTime { get; set; } public DateTimeOffset? UnlockedDateTime { get; set; } public DateTimeOffset? CompletedDateTime { get; set; } public DateTimeOffset? ArchiveDateTime { get; set; } diff --git a/src/Unlimotion.Server.ServiceModel/molds/Tasks/TaskItemMold.cs b/src/Unlimotion.Server.ServiceModel/molds/Tasks/TaskItemMold.cs index 330ba261cec66cd1268f29ef98e2cc3f44c8e506..dbdcd508f33af3f81062326cbe794928ecdf31d9 100644 GIT binary patch delta 38 ucmX@5GfQuS4Hvf^iy@0Giyey*i}mD#9EOtvxD{AK844IuHlO9Ji24cBHKZay9W9tH!M diff --git a/src/Unlimotion.TaskTreeManager/TaskTreeManager.cs b/src/Unlimotion.TaskTreeManager/TaskTreeManager.cs index c691391..af46514 100644 --- a/src/Unlimotion.TaskTreeManager/TaskTreeManager.cs +++ b/src/Unlimotion.TaskTreeManager/TaskTreeManager.cs @@ -29,6 +29,7 @@ await IsCompletedAsync(async Task () => try { change.Version = 1; + change.UpdatedDateTime ??= change.CreatedDateTime; await Storage.Save(change); result.AddOrUpdate(change); @@ -53,6 +54,7 @@ await IsCompletedAsync(async Task () => if (newTaskId is null) { change.Version = 1; + change.UpdatedDateTime ??= change.CreatedDateTime; await Storage.Save(change); newTaskId = change.Id; result.AddOrUpdate(change); @@ -104,6 +106,7 @@ await IsCompletedAsync(async Task () => if (newTaskId is null) { change.Version = 1; + change.UpdatedDateTime ??= change.CreatedDateTime; await Storage.Save(change); newTaskId = change.Id; result.AddOrUpdate(change); @@ -301,6 +304,7 @@ await IsCompletedAsync(async Task () => else { // Regular update without IsCompleted change + change.UpdatedDateTime = GetNextUpdatedDateTime(change); await Storage.Save(change); result.AddOrUpdate(change); } @@ -890,6 +894,7 @@ await IsCompletedAsync(async () => // Save the cloned task clone.Version = 1; + clone.UpdatedDateTime ??= clone.CreatedDateTime; await Storage.Save(clone); result.AddOrUpdate(clone); @@ -952,6 +957,7 @@ await IsCompletedAsync(async () => } // Save the updated task + task.UpdatedDateTime = GetNextUpdatedDateTime(task); await Storage.Save(task); result.AddOrUpdate(task); @@ -968,4 +974,15 @@ await IsCompletedAsync(async () => return result.Values.ToList(); } + + private static DateTimeOffset GetNextUpdatedDateTime(TaskItem task) + { + var now = DateTimeOffset.Now; + if (task.UpdatedDateTime.HasValue && now <= task.UpdatedDateTime.Value) + { + return task.UpdatedDateTime.Value.AddSeconds(1); + } + + return now; + } } diff --git a/src/Unlimotion.Test/InMemoryStorage.cs b/src/Unlimotion.Test/InMemoryStorage.cs index 69d1974..2c33aec 100644 --- a/src/Unlimotion.Test/InMemoryStorage.cs +++ b/src/Unlimotion.Test/InMemoryStorage.cs @@ -55,6 +55,7 @@ public async Task Save(TaskItem taskItem) IsCompleted = taskItem.IsCompleted, IsCanBeCompleted = taskItem.IsCanBeCompleted, CreatedDateTime = taskItem.CreatedDateTime, + UpdatedDateTime = taskItem.UpdatedDateTime, UnlockedDateTime = taskItem.UnlockedDateTime, CompletedDateTime = taskItem.CompletedDateTime, ArchiveDateTime = taskItem.ArchiveDateTime, diff --git a/src/Unlimotion.ViewModel/FileDbWatcher.cs b/src/Unlimotion.ViewModel/FileDbWatcher.cs index 1e6e2b5..a346576 100644 --- a/src/Unlimotion.ViewModel/FileDbWatcher.cs +++ b/src/Unlimotion.ViewModel/FileDbWatcher.cs @@ -27,7 +27,10 @@ public void SetEnable(bool enable) lock (itLockEnable) { isEnable = enable; - watcher?.EnableRaisingEvents = enable; + if (watcher != null) + { + watcher.EnableRaisingEvents = enable; + } } } diff --git a/src/Unlimotion.ViewModel/MainWindowViewModel.cs b/src/Unlimotion.ViewModel/MainWindowViewModel.cs index aeb9651..cfe1f07 100644 --- a/src/Unlimotion.ViewModel/MainWindowViewModel.cs +++ b/src/Unlimotion.ViewModel/MainWindowViewModel.cs @@ -21,6 +21,7 @@ public class MainWindowViewModel : DisposableList private bool _isCompletedTabInitialized; private bool _isArchivedTabInitialized; private bool _isLastCreatedTabInitialized; + private bool _isLastUpdatedTabInitialized; private bool _isRoadmapTabInitialized; private bool _isUnlockedTabInitialized; private bool _isLastOpenedTabInitialized; @@ -50,6 +51,7 @@ public MainWindowViewModel( CompletedItems = EmptyTaskWrappers; ArchivedItems = EmptyTaskWrappers; LastCreatedItems = EmptyTaskWrappers; + LastUpdatedItems = EmptyTaskWrappers; LastOpenedItems = EmptyTaskWrappers; Graph.SetMainWindowViewModel(this); Graph.Search = Search; @@ -191,6 +193,14 @@ private void RegisterCommands() }) .AddToDispose(connectionDisposableList); + this.WhenAnyValue(m => m.CurrentLastUpdated) + .Subscribe(m => + { + if (m != null && CurrentTaskItem != m?.TaskItem) + CurrentTaskItem = m?.TaskItem; + }) + .AddToDispose(connectionDisposableList); + this.WhenAnyValue(m => m.CurrentLastOpenedItem) .Subscribe(m => { @@ -199,9 +209,17 @@ private void RegisterCommands() }) .AddToDispose(connectionDisposableList); - this.WhenAnyValue(m => m.AllTasksMode, m => m.UnlockedMode, m => m.CompletedMode, m => m.ArchivedMode, m => m.GraphMode, m => m.LastCreatedMode, m => m.LastOpenedMode) - .Subscribe(a => - { + Observable.Merge( + this.WhenAnyValue(m => m.AllTasksMode), + this.WhenAnyValue(m => m.UnlockedMode), + this.WhenAnyValue(m => m.CompletedMode), + this.WhenAnyValue(m => m.ArchivedMode), + this.WhenAnyValue(m => m.GraphMode), + this.WhenAnyValue(m => m.LastCreatedMode), + this.WhenAnyValue(m => m.LastUpdatedMode), + this.WhenAnyValue(m => m.LastOpenedMode)) + .Subscribe(_ => + { SelectCurrentTask(); }) .AddToDispose(connectionDisposableList); @@ -226,6 +244,7 @@ public async Task Connect() _isCompletedTabInitialized = false; _isArchivedTabInitialized = false; _isLastCreatedTabInitialized = false; + _isLastUpdatedTabInitialized = false; _isRoadmapTabInitialized = false; _isUnlockedTabInitialized = false; _isLastOpenedTabInitialized = false; @@ -676,8 +695,7 @@ bool Predicate(TaskItemViewModel task) }); #endregion Unlocked - - //Bind Completed + this.WhenAnyValue(m => m.LastCreatedDateFilter.CurrentOption, m => m.LastCreatedDateFilter.IsCustom) .Subscribe(filter => { @@ -702,6 +720,31 @@ bool Predicate(TaskItemViewModel task) return (Func)Predicate; }); + this.WhenAnyValue(m => m.LastUpdatedDateFilter.CurrentOption, m => m.LastUpdatedDateFilter.IsCustom) + .Subscribe(filter => + { + if (!filter.Item2) + LastUpdatedDateFilter.SetDateTimes(filter.Item1); + }); + + var lastUpdatedDateFilter = this.WhenAnyValue(m => m.LastUpdatedDateFilter.From, + m => m.LastUpdatedDateFilter.To, m => m.LastUpdatedDateFilter.IsCustom) + .Select(filter => + { + bool Predicate(TaskItemViewModel task) + { + if (filter.Item1 == null || filter.Item2 == null) + return true; + + var dateTime = task.UpdatedDateTime?.Add(DateTimeOffset.Now.Offset).Date; + return filter.Item1 <= dateTime && dateTime <= filter.Item2; + } + + return (Func)Predicate; + }); + + //Bind Completed + void ActivateCompletedProjection() { if (_isCompletedTabInitialized) @@ -820,6 +863,46 @@ void ActivateLastCreatedProjection() LastCreatedItems = _lastCreatedItems; } + void ActivateLastUpdatedProjection() + { + if (_isLastUpdatedTabInitialized) + { + return; + } + + _isLastUpdatedTabInitialized = true; + taskRepository.Tasks + .Connect() + .AutoRefreshOnObservable(m => m.WhenAny(m => m.IsCanBeCompleted, m => m.IsCompleted, + m => m.UnlockedDateTime, (c, d, u) => c.Value && (d.Value == false))) + .AutoRefreshOnObservable(m => m.WhenAnyValue( + x => x.Title, + x => x.Description, + x => x.GetAllEmoji)) + .Filter(taskFilter) + .Filter(lastUpdatedDateFilter) + .Filter(emojiFilter) + .Filter(emojiExcludeFilter) + .Filter(searchTopFilter) + .Transform(item => + { + var actions = new TaskWrapperActions + { + ChildSelector = m => m.ContainsTasks.ToObservableChangeSet(), + RemoveAction = RemoveTask, + GetBreadScrumbs = BredScrumbsAlgorithms.FirstTaskParent, + }; + var wrapper = new TaskWrapperViewModel(null, item, actions); + return wrapper; + }) + .SortBy(e => e.TaskItem.UpdatedDateTime, SortDirection.Descending) + .Bind(out _lastUpdatedItems) + .Subscribe() + .AddToDispose(connectionDisposableList); + + LastUpdatedItems = _lastUpdatedItems; + } + void ActivateRoadmapProjection() { if (_isRoadmapTabInitialized) @@ -902,6 +985,12 @@ void ActivateRoadmapProjection() .Subscribe(_ => ActivateLastCreatedProjection()) .AddToDispose(connectionDisposableList); + this.WhenAnyValue(m => m.LastUpdatedMode) + .Where(mode => mode) + .Take(1) + .Subscribe(_ => ActivateLastUpdatedProjection()) + .AddToDispose(connectionDisposableList); + this.WhenAnyValue(m => m.UnlockedMode) .Where(mode => mode) .Take(1) @@ -1086,7 +1175,7 @@ void ActivateLastOpenedProjection() public void SelectCurrentTask() { - if (AllTasksMode ^ UnlockedMode ^ CompletedMode ^ ArchivedMode ^ GraphMode ^ LastCreatedMode ^ LastOpenedMode) + if (AllTasksMode ^ UnlockedMode ^ CompletedMode ^ ArchivedMode ^ GraphMode ^ LastCreatedMode ^ LastUpdatedMode ^ LastOpenedMode) { if (AllTasksMode) { @@ -1124,6 +1213,11 @@ public void SelectCurrentTask() if (CurrentLastCreated?.TaskItem != CurrentTaskItem) CurrentLastCreated = FindTaskWrapperViewModel(CurrentTaskItem, LastCreatedItems); } + else if (LastUpdatedMode) + { + if (CurrentLastUpdated?.TaskItem != CurrentTaskItem) + CurrentLastUpdated = FindTaskWrapperViewModel(CurrentTaskItem, LastUpdatedItems); + } else if (LastOpenedMode) { if (CurrentLastOpenedItem?.TaskItem != CurrentTaskItem) @@ -1234,6 +1328,7 @@ private void ExpandParentNodesForTask(TaskItemViewModel taskItem) public bool GraphMode { get; set; } public bool SettingsMode { get; set; } public bool LastCreatedMode { get; set; } + public bool LastUpdatedMode { get; set; } public bool LastOpenedMode { get; set; } public INotificationManagerWrapper ManagerWrapper { get; } @@ -1257,6 +1352,9 @@ private void ExpandParentNodesForTask(TaskItemViewModel taskItem) private ReadOnlyObservableCollection _lastCreatedItems; public ReadOnlyObservableCollection LastCreatedItems { get; set; } + private ReadOnlyObservableCollection _lastUpdatedItems; + public ReadOnlyObservableCollection LastUpdatedItems { get; set; } + public ReadOnlyObservableCollection _lastOpenedItems; public ReadOnlyObservableCollection LastOpenedItems { get; set; } @@ -1271,6 +1369,7 @@ private void ExpandParentNodesForTask(TaskItemViewModel taskItem) public TaskWrapperViewModel CurrentCompletedItem { get; set; } = null!; public TaskWrapperViewModel CurrentArchivedItem { get; set; } = null!; public TaskWrapperViewModel CurrentLastCreated { get; set; } = null!; + public TaskWrapperViewModel CurrentLastUpdated { get; set; } = null!; public TaskWrapperViewModel CurrentGraphItem { get; set; } = null!; public TaskWrapperViewModel CurrentLastOpenedItem { get; set; } = null!; @@ -1324,6 +1423,7 @@ private void ExpandParentNodesForTask(TaskItemViewModel taskItem) public DateFilter CompletedDateFilter { get; set; } = new(); public DateFilter ArchivedDateFilter { get; set; } = new(); public DateFilter LastCreatedDateFilter { get; set; } = new(); + public DateFilter LastUpdatedDateFilter { get; set; } = new(); public static ReadOnlyObservableCollection DateFilterDefinitions { get; set; } = DateFilterDefinition.GetDefinitions(); public object TabItems { get; } = null!; diff --git a/src/Unlimotion.ViewModel/SortDefinition.cs b/src/Unlimotion.ViewModel/SortDefinition.cs index 352e665..0a401d5 100644 --- a/src/Unlimotion.ViewModel/SortDefinition.cs +++ b/src/Unlimotion.ViewModel/SortDefinition.cs @@ -26,6 +26,7 @@ public static IEnumerable GetDefinitions() new(w => w.TaskItem.ArchiveDateTime), new(w => w.TaskItem.UnlockedDateTime), new(w => w.TaskItem.CreatedDateTime), + new(w => w.TaskItem.UpdatedDateTime), } }; //Эмодзи @@ -55,6 +56,24 @@ public static IEnumerable GetDefinitions() new(w => w.TaskItem.CreatedDateTime, SortDirection.Descending) } }; + //По дате обновления Asc + yield return new SortDefinition + { + Name = "Updated Ascending", + Comparer = new SortExpressionComparer + { + new(w => w.TaskItem.UpdatedDateTime) + } + }; + //По дате обновления Des + yield return new SortDefinition + { + Name = "Updated Descending", + Comparer = new SortExpressionComparer + { + new(w => w.TaskItem.UpdatedDateTime, SortDirection.Descending) + } + }; //По дате разблокировки Asc yield return new SortDefinition { diff --git a/src/Unlimotion.ViewModel/TaskItemViewModel.cs b/src/Unlimotion.ViewModel/TaskItemViewModel.cs index 184f174..ca6c66e 100644 --- a/src/Unlimotion.ViewModel/TaskItemViewModel.cs +++ b/src/Unlimotion.ViewModel/TaskItemViewModel.cs @@ -402,6 +402,7 @@ public TaskItem Model Title = Title, Description = Description, CreatedDateTime = CreatedDateTime, + UpdatedDateTime = UpdatedDateTime, UnlockedDateTime = UnlockedDateTime, CompletedDateTime = CompletedDateTime, ArchiveDateTime = ArchiveDateTime, @@ -434,6 +435,7 @@ public TaskItem Model public bool? IsCompleted { get; set; } public int Version { get; set; } public DateTimeOffset CreatedDateTime { get; set; } + public DateTimeOffset? UpdatedDateTime { get; set; } public DateTimeOffset? UnlockedDateTime { get; set; } public DateTimeOffset? CompletedDateTime { get; set; } public DateTimeOffset? ArchiveDateTime { get; set; } @@ -616,6 +618,7 @@ public void Update(TaskItem taskItem) if (Title != taskItem.Title) Title = taskItem.Title; if (Description != taskItem.Description) Description = taskItem.Description; if (CreatedDateTime != taskItem.CreatedDateTime) CreatedDateTime = taskItem.CreatedDateTime; + if (UpdatedDateTime != taskItem.UpdatedDateTime) UpdatedDateTime = taskItem.UpdatedDateTime; if (UnlockedDateTime != taskItem.UnlockedDateTime) UnlockedDateTime = taskItem.UnlockedDateTime; if (CompletedDateTime != taskItem.CompletedDateTime) CompletedDateTime = taskItem.CompletedDateTime; if (ArchiveDateTime != taskItem.ArchiveDateTime) ArchiveDateTime = taskItem.ArchiveDateTime; diff --git a/src/Unlimotion/Views/MainControl.axaml b/src/Unlimotion/Views/MainControl.axaml index ce35960..6845bd5 100644 --- a/src/Unlimotion/Views/MainControl.axaml +++ b/src/Unlimotion/Views/MainControl.axaml @@ -151,7 +151,7 @@ Command="{Binding RemoveCommand}"/> SelectionMode="Single"> - + + + + + + + + + + + + + + + + + +