diff --git a/src/ui/osx/TogglDesktop/Features/Timeline/Models/TimelineData.swift b/src/ui/osx/TogglDesktop/Features/Timeline/Models/TimelineData.swift index bfde4df8ed..ef8bee7832 100644 --- a/src/ui/osx/TogglDesktop/Features/Timeline/Models/TimelineData.swift +++ b/src/ui/osx/TogglDesktop/Features/Timeline/Models/TimelineData.swift @@ -209,7 +209,7 @@ final class TimelineData { let started = entry.timeEntry.started else { return } // Set the end time as a start time of selected entry - DesktopLibraryBridge.shared().updateTimeEntryWithEnd(atTimestamp: started.timeIntervalSince1970 - 1, + DesktopLibraryBridge.shared().updateTimeEntryWithEnd(atTimestamp: started.timeIntervalSince1970, guid: firstEntry.timeEntry.guid) } @@ -222,7 +222,7 @@ final class TimelineData { let endAt = firstEntry.timeEntry.ended else { return } // Set the start time as a stop time of First entry - DesktopLibraryBridge.shared().updateTimeEntryWithStart(atTimestamp: endAt.timeIntervalSince1970 + 1, + DesktopLibraryBridge.shared().updateTimeEntryWithStart(atTimestamp: endAt.timeIntervalSince1970, guid: entry.timeEntry.guid, keepEndTimeFixed: true) } diff --git a/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineBlockViewModel.cs b/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineBlockViewModel.cs index 8313ab6602..c33c207b75 100644 --- a/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineBlockViewModel.cs +++ b/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineBlockViewModel.cs @@ -38,7 +38,8 @@ public GapTimeEntryBlock(Func addNewTimeEntry) public class TimeEntryBlock : TimelineBlockViewModel { [Reactive] - public bool ShowDescription { get; set; } + public bool IsOverlapping { get; set; } + public bool ShowDescription { [ObservableAsProperty] get; } public string Color => _timeEntry.Color; public string Description => _timeEntry.Description.IsNullOrEmpty() ? "No Description" : _timeEntry.Description; public string ProjectName => _timeEntry.ProjectLabel; @@ -88,6 +89,9 @@ public TimeEntryBlock(Toggl.TogglTimeEntryView te, int hourHeight, DateTime date .Select(h => h >= TimelineConstants.MinResizableTimeEntryBlockHeight) .Where(_ => !IsDragged) .ToPropertyEx(this, x => x.IsResizable); + this.WhenAnyValue(x => x.IsOverlapping) + .Select(isOverlapping => !isOverlapping && Height >= TimelineConstants.MinShowTEDescriptionHeight) + .ToPropertyEx(this, x => x.ShowDescription); } public void ChangeStartTime() diff --git a/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineViewModel.cs b/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineViewModel.cs index 1160d76db3..1d48c9eced 100644 --- a/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineViewModel.cs +++ b/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineViewModel.cs @@ -14,6 +14,7 @@ namespace TogglDesktop.ViewModels public class TimelineViewModel : ReactiveObject { private DateTime _lastDateLoaded; + private List SortedTimeEntryBlocks { [ObservableAsProperty] get; } public TimelineViewModel() { @@ -51,6 +52,11 @@ public TimelineViewModel() CreateFromEnd = ReactiveCommand.Create(() => TimelineUtils.CreateAndEditTimeEntry(ActiveTimeEntryBlock.Ended, ActiveTimeEntryBlock.Ended + TimelineConstants.DefaultTimeEntryLengthInSeconds), isNotRunningObservable); StartFromEnd = ReactiveCommand.Create(() => TimelineUtils.CreateAndEditRunningTimeEntryFrom(ActiveTimeEntryBlock.Ended), isNotRunningObservable); Delete = ReactiveCommand.Create(() => ActiveTimeEntryBlock.DeleteTimeEntry(), activeBlockObservable.Select(next => next != null)); + var isOverlapping = activeBlockObservable.Select(next => next != null && next.IsOverlapping); + ChangeFirstTimeEntryStopCommand = + ReactiveCommand.Create(() => ChangeFirstEntryStop(ActiveTimeEntryBlock, SortedTimeEntryBlocks), isOverlapping); + ChangeLastTimeEntryStartCommand = + ReactiveCommand.Create(() => ChangeLastEntryStart(ActiveTimeEntryBlock, SortedTimeEntryBlocks), isOverlapping); var scaleModeObservable = this.WhenAnyValue(x => x.SelectedScaleMode); scaleModeObservable.Subscribe(_ => HourHeightView = TimelineConstants.ScaleModes[SelectedScaleMode] * GetHoursInLine(SelectedScaleMode)); @@ -74,7 +80,14 @@ public TimelineViewModel() tuple.TimeEntries.Where(b => b.Key != tuple.Running?.GUID) .ToDictionary(pair => pair.Key, pair => pair.Value)) .ToPropertyEx(this, x => x.TimeEntryBlocks); - blocksObservable.Select(blocks => GenerateGapTimeEntryBlocks(blocks.Values.ToList())) + var sortedTimeEntriesObservable = blocksObservable.Select(blocks => + { + var timeEntries = blocks.Values.ToList(); + timeEntries.Sort((te1, te2) => te1.VerticalOffset.CompareTo(te2.VerticalOffset)); + return timeEntries; + }); + sortedTimeEntriesObservable.ToPropertyEx(this, x => x.SortedTimeEntryBlocks); + sortedTimeEntriesObservable.Select(GenerateGapTimeEntryBlocks) .ToPropertyEx(this, x => x.GapTimeEntryBlocks); blocksWithRunningObservable .Select(tuple => @@ -218,7 +231,7 @@ private static Dictionary ConvertTimeEntriesToBlocks(Lis { Height = height, VerticalOffset = ConvertTimeIntervalToHeight(selectedDate, startTime, selectedScaleMode), - ShowDescription = true + IsOverlapping = false }; if (entry.Started < ended) { @@ -262,11 +275,11 @@ private static Dictionary ConvertTimeEntriesToBlocks(Lis offsets.Add(curOffset); curOffset += TimelineConstants.TimeEntryBlockWidth+TimelineConstants.GapBetweenOverlappingTEs; } - if (usedNumOfOffsets > 0 || item.Block.Height < TimelineConstants.MinShowTEDescriptionHeight) + if (usedNumOfOffsets > 0) { - item.Block.ShowDescription = false; + item.Block.IsOverlapping = true; if (prevLayerBlock != null) - prevLayerBlock.ShowDescription = false; + prevLayerBlock.IsOverlapping = true; } item.Block.HorizontalOffset = offsets.Min(); offsets.Remove(offsets.Min()); @@ -293,7 +306,6 @@ public static double ConvertTimeIntervalToHeight(DateTime start, DateTime end, i private static List GenerateGapTimeEntryBlocks(List timeEntries) { var gaps = new List(); - timeEntries.Sort((te1,te2) => te1.VerticalOffset.CompareTo(te2.VerticalOffset)); TimeEntryBlock lastTimeEntry = null; foreach (var entry in timeEntries) { @@ -350,6 +362,30 @@ public static string AddNewTimeEntry(double offset, double height, int scaleMode return timeEntryId; } + public static void ChangeFirstEntryStop(TimeEntryBlock item, List blocks) + { + var (first, last) = GetOverlappingPair(item, blocks); + if (last == null) return; + Toggl.SetTimeEntryEndTimeStamp(first.TimeEntryId, (long)last.Started); + } + + public static void ChangeLastEntryStart(TimeEntryBlock item, List blocks) + { + var (first, last) = GetOverlappingPair(item, blocks); + if (last == null) return; + Toggl.SetTimeEntryStartTimeStamp(last.TimeEntryId, (long)first.Ended); + } + + private static (TimeEntryBlock First, TimeEntryBlock Last) GetOverlappingPair(TimeEntryBlock item, IEnumerable blocks) + { + var overlapping = blocks.FirstOrDefault(b => b.TimeEntryId != item.TimeEntryId && b.IsOverlappingWith(item)); + if (overlapping == null) return (item, null); + + var first = item.Started < overlapping.Started ? item : overlapping; + var last = item.Started < overlapping.Started ? overlapping : item; + return (first, last); + } + [Reactive] public int SelectedScaleMode { get; private set; } = 0; [Reactive] @@ -402,6 +438,8 @@ public static string AddNewTimeEntry(double offset, double height, int scaleMode public ReactiveCommand CreateFromEnd { get; } public ReactiveCommand StartFromEnd { get; } public ReactiveCommand Delete { get; } + public ReactiveCommand ChangeFirstTimeEntryStopCommand { get; } + public ReactiveCommand ChangeLastTimeEntryStartCommand { get; } public class ActivityBlock { diff --git a/src/ui/windows/TogglDesktop/TogglDesktop/ui/views/Timeline.xaml b/src/ui/windows/TogglDesktop/TogglDesktop/ui/views/Timeline.xaml index e3b20a3bc8..dff55bebe4 100644 --- a/src/ui/windows/TogglDesktop/TogglDesktop/ui/views/Timeline.xaml +++ b/src/ui/windows/TogglDesktop/TogglDesktop/ui/views/Timeline.xaml @@ -23,6 +23,9 @@ + + + diff --git a/src/ui/windows/TogglDesktop/TogglDesktop/utilities/TimelineUtils.cs b/src/ui/windows/TogglDesktop/TogglDesktop/utilities/TimelineUtils.cs index 5dd6d105f3..20bc8bcb2b 100644 --- a/src/ui/windows/TogglDesktop/TogglDesktop/utilities/TimelineUtils.cs +++ b/src/ui/windows/TogglDesktop/TogglDesktop/utilities/TimelineUtils.cs @@ -1,4 +1,5 @@ using System; +using TogglDesktop.ViewModels; namespace TogglDesktop { @@ -38,5 +39,8 @@ public static void CreateAndEditTimeEntry(ulong started, ulong ended) public static DateTime StartTime(this Toggl.TimelineChunkView chunk) => Toggl.DateTimeFromUnix(chunk.Started); public static DateTime EndTime(this Toggl.TimelineChunkView chunk) => Toggl.DateTimeFromUnix(chunk.Ended); + + public static bool IsOverlappingWith(this TimeEntryBlock first, TimeEntryBlock second) => + first.Started < second.Ended && second.Started < first.Ended; } }