diff --git a/src/ui/windows/TogglDesktop/TogglDesktop/ui/Resources/DesignUpdate/Buttons.xaml b/src/ui/windows/TogglDesktop/TogglDesktop/ui/Resources/DesignUpdate/Buttons.xaml index 44ad9c7e31..12d1d9d168 100644 --- a/src/ui/windows/TogglDesktop/TogglDesktop/ui/Resources/DesignUpdate/Buttons.xaml +++ b/src/ui/windows/TogglDesktop/TogglDesktop/ui/Resources/DesignUpdate/Buttons.xaml @@ -358,9 +358,28 @@ RadiusX="15" RadiusY="7"> - - - + + + + + + + + + + \ No newline at end of file diff --git a/src/ui/windows/TogglDesktop/TogglDesktop/ui/Resources/TimelineConstants.cs b/src/ui/windows/TogglDesktop/TogglDesktop/ui/Resources/TimelineConstants.cs index 9e274923e0..f5fc70dbfe 100644 --- a/src/ui/windows/TogglDesktop/TogglDesktop/ui/Resources/TimelineConstants.cs +++ b/src/ui/windows/TogglDesktop/TogglDesktop/ui/Resources/TimelineConstants.cs @@ -13,6 +13,7 @@ public static class TimelineConstants public const double TimeEntryBlockWidth = 20; public const double GapBetweenOverlappingTEs = 5; public const double AcceptableBlocksOverlap = 1e-5; + public const double MinGapTimeEntryHeight = 10; public static IReadOnlyDictionary ScaleModes { get; } = new Dictionary() { diff --git a/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineBlockViewModel.cs b/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineBlockViewModel.cs index 2b683b8757..b54c1d705b 100644 --- a/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineBlockViewModel.cs +++ b/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineBlockViewModel.cs @@ -36,25 +36,19 @@ public GapTimeEntryBlock(Func addNewTimeEntry) public class TimeEntryBlock : TimelineBlockViewModel { - [Reactive] - public string Color { get; set; } [Reactive] public bool ShowDescription { get; set; } - [Reactive] - public string Description { get; set; } - [Reactive] - public string ProjectName { get; set; } - [Reactive] - public string ClientName { get; set; } - public string TaskName { get; set; } - [Reactive] - public bool HasTag { get; set; } - [Reactive] - public bool IsBillable { get; set; } + public string Color => _timeEntry.Color; + public string Description => _timeEntry.Description.IsNullOrEmpty() ? "No Description" : _timeEntry.Description; + public string ProjectName => _timeEntry.ProjectLabel; + public string ClientName => _timeEntry.ClientLabel; + public string TaskName => _timeEntry.TaskLabel; + public bool HasTag => !_timeEntry.Tags.IsNullOrEmpty(); + public bool IsBillable => _timeEntry.Billable; public string Duration { [ObservableAsProperty]get; } public string StartEndCaption { [ObservableAsProperty]get; } public ReactiveCommand OpenEditView { get; } - public string TimeEntryId { get; } + public string TimeEntryId => _timeEntry.GUID; [Reactive] public bool IsEditViewOpened { get; set; } @@ -64,15 +58,19 @@ public class TimeEntryBlock : TimelineBlockViewModel [Reactive] public bool IsDragged { get; set; } - public DateTime DateCreated { get; } + public ulong Started => _timeEntry.Started; + public ulong Ended => _timeEntry.Ended; + + private DateTime DateCreated { get; } private readonly double _hourHeight; + private readonly Toggl.TogglTimeEntryView _timeEntry; - public TimeEntryBlock(string timeEntryId, int hourHeight, DateTime date) + public TimeEntryBlock(Toggl.TogglTimeEntryView te, int hourHeight, DateTime date) { _hourHeight = hourHeight; DateCreated = date; - TimeEntryId = timeEntryId; + _timeEntry = te; OpenEditView = ReactiveCommand.Create(() => Toggl.Edit(TimeEntryId, false, Toggl.Description)); var startEndObservable = this.WhenAnyValue(x => x.VerticalOffset, x => x.Height, (offset, height) => (Started: TimelineUtils.ConvertOffsetToDateTime(offset, date, _hourHeight), Ended: TimelineUtils.ConvertOffsetToDateTime(offset + height, date, _hourHeight))); diff --git a/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineViewModel.cs b/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineViewModel.cs index 708b8dd28c..814f1d638c 100644 --- a/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineViewModel.cs +++ b/src/ui/windows/TogglDesktop/TogglDesktop/ui/ViewModels/TimelineViewModel.cs @@ -68,8 +68,12 @@ 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(), SelectedScaleMode, SelectedDate)) + blocksObservable.Select(blocks => GenerateGapTimeEntryBlocks(blocks.Values.ToList())) .ToPropertyEx(this, x => x.GapTimeEntryBlocks); + blocksWithRunningObservable + .Select(tuple => + GenerateRunningGapBlock(tuple.TimeEntries.Values, tuple.Running, CurrentTimeOffset, SelectedDate)) + .ToPropertyEx(this, x => x.RunningGapTimeEntryBlock); this.WhenAnyValue(x => x.TimeEntryBlocks) .Where(blocks => blocks != null && blocks.Any()) @@ -88,10 +92,10 @@ public TimelineViewModel() Observable.Timer(TimeSpan.Zero,TimeSpan.FromMinutes(1)) .Select(_ => ConvertTimeIntervalToHeight(DateTime.Today, DateTime.Now, SelectedScaleMode)) .Subscribe(h => CurrentTimeOffset = h); - this.WhenAnyValue(x => x.CurrentTimeOffset).Where(_ => RunningTimeEntryBlock != null) - .Select(off => Math.Max(TimelineConstants.MinTimeEntryBlockHeight, - CurrentTimeOffset - RunningTimeEntryBlock.VerticalOffset)) - .Subscribe(h => RunningTimeEntryBlock.Height = h); + this.WhenAnyValue(x => x.CurrentTimeOffset).Select(offset => (Offset: offset, + Block: RunningTimeEntryBlock as TimelineBlockViewModel ?? RunningGapTimeEntryBlock)) + .Where(tuple => tuple.Block != null) + .Subscribe(tuple => tuple.Block.Height = tuple.Offset - tuple.Block.VerticalOffset); this.WhenAnyValue(x => x.TimeEntryBlocks, x => x.RunningTimeEntryBlock, x => x.IsTodaySelected, (blocks, running, isToday) => blocks?.Any() == true || (running != null && isToday)) .ToPropertyEx(this, x => x.AnyTimeEntries); @@ -205,18 +209,11 @@ private static Dictionary ConvertTimeEntriesToBlocks(Lis ? TimelineUtils.ConvertOffsetToUnixTime(currentTimeOffset, selectedDate, TimelineConstants.ScaleModes[selectedScaleMode]) : entry.Ended; var height = ConvertTimeIntervalToHeight(startTime, Toggl.DateTimeFromUnix(ended), selectedScaleMode); - var block = new TimeEntryBlock(entry.GUID, TimelineConstants.ScaleModes[selectedScaleMode], selectedDate) + var block = new TimeEntryBlock(entry, TimelineConstants.ScaleModes[selectedScaleMode], selectedDate) { Height = height, VerticalOffset = ConvertTimeIntervalToHeight(selectedDate, startTime, selectedScaleMode), - Color = entry.Color, - Description = entry.Description.IsNullOrEmpty() ? "No Description" : entry.Description, - ProjectName = entry.ProjectLabel, - ClientName = entry.ClientLabel, - TaskName = entry.TaskLabel, - ShowDescription = true, - HasTag = !entry.Tags.IsNullOrEmpty(), - IsBillable = entry.Billable + ShowDescription = true }; if (entry.Started < ended) { @@ -239,7 +236,7 @@ private static Dictionary ConvertTimeEntriesToBlocks(Lis var time1 = te1.Type == TimeStampType.End ? te1.Block.Bottom : te1.Block.VerticalOffset; var time2 = te2.Type == TimeStampType.End ? te2.Block.Bottom : te2.Block.VerticalOffset; var res = time1 - time2; - if (Math.Abs(res) < TimelineConstants.AcceptableBlocksOverlap) + if (res.IsNearEqual(0, TimelineConstants.AcceptableBlocksOverlap)) { var getPriority = new Func(t => t == TimeStampType.End ? 0 : t == TimeStampType.Empty ? 1 : 2); @@ -288,7 +285,7 @@ public static double ConvertTimeIntervalToHeight(DateTime start, DateTime end, i return timeInterval * TimelineConstants.ScaleModes[scaleMode] / 60; } - private static List GenerateGapTimeEntryBlocks(List timeEntries, int selectedScaleMode, DateTime selectedDate) + private static List GenerateGapTimeEntryBlocks(List timeEntries) { var gaps = new List(); timeEntries.Sort((te1,te2) => te1.VerticalOffset.CompareTo(te2.VerticalOffset)); @@ -297,23 +294,47 @@ private static List GenerateGapTimeEntryBlocks(List lastTimeEntry.Bottom) { - var block = new GapTimeEntryBlock((offset, height) => AddNewTimeEntry(offset, height, selectedScaleMode, selectedDate)) + var gapStart = lastTimeEntry.Ended + 1; + var block = new GapTimeEntryBlock((offset, height) => Toggl.CreateEmptyTimeEntry(gapStart, entry.Started)) { Height = entry.VerticalOffset - lastTimeEntry.Bottom, VerticalOffset = lastTimeEntry.Bottom, HorizontalOffset = 0 }; - if (block.Height > 10) // Don't display to small gaps not to obstruct the view + if (block.Height >= TimelineConstants.MinGapTimeEntryHeight) // Don't display too small gaps not to obstruct the view gaps.Add(block); } lastTimeEntry = lastTimeEntry == null || entry.Bottom > lastTimeEntry.Bottom ? entry : lastTimeEntry; } - + return gaps; } + private static GapTimeEntryBlock GenerateRunningGapBlock(IEnumerable timeEntries, Toggl.TogglTimeEntryView? running, + double curTimeOffset, DateTime selectedDate) + { + if (running != null || selectedDate.Date != DateTime.Today) return null; + + var lastTimeEntry = timeEntries.Aggregate(default(TimeEntryBlock), + (max, next) => max == null || next.Ended > max.Ended ? next : max); + if (lastTimeEntry != null && curTimeOffset >= lastTimeEntry.Bottom + TimelineConstants.MinGapTimeEntryHeight) + return new GapTimeEntryBlock( + (offset, height) => + { + var id = Toggl.Start("", "", 0, 0, "", ""); + Toggl.SetTimeEntryStartTimeStamp(id, (long)lastTimeEntry.Ended+1); + return id; + }) + { + Height = curTimeOffset - lastTimeEntry.Bottom, + VerticalOffset = lastTimeEntry.Bottom, + HorizontalOffset = 0 + }; + return null; + } + public static string AddNewTimeEntry(double offset, double height, int scaleMode, DateTime date) { var started = TimelineUtils.ConvertOffsetToUnixTime(offset, date, @@ -355,6 +376,8 @@ public static string AddNewTimeEntry(double offset, double height, int scaleMode public List GapTimeEntryBlocks { [ObservableAsProperty] get; } + public GapTimeEntryBlock RunningGapTimeEntryBlock { [ObservableAsProperty] get; } + [Reactive] public string SelectedForEditTEId { get; set; } public ReactiveCommand IncreaseScale { get; } diff --git a/src/ui/windows/TogglDesktop/TogglDesktop/ui/views/Timeline.xaml b/src/ui/windows/TogglDesktop/TogglDesktop/ui/views/Timeline.xaml index 6290fa8709..aac1ae6ea1 100644 --- a/src/ui/windows/TogglDesktop/TogglDesktop/ui/views/Timeline.xaml +++ b/src/ui/windows/TogglDesktop/TogglDesktop/ui/views/Timeline.xaml @@ -151,10 +151,11 @@ MouseDown="OnTimeEntryCanvasMouseDown" MouseMove="OnTimeEntryCanvasMouseMove" MouseUp="OnTimeEntryCanvasMouseUp"/> - + + Canvas.Left="{Binding HorizontalOffset}" + Visibility="{Binding Path=., Converter={StaticResource NullToCollapsedConverter}}"> + Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 1; #endregion environment -} + + #region working with numbers + + public static bool IsNearEqual(this double first, double second, double precision = 1e-5) + { + return Math.Abs(first - second) <= precision; + } + + #endregion + } }