private static List <TimeEntryBlock> GenerateGapTimeEntryBlocks(List <Toggl.TogglTimeEntryView> timeEntries, int selectedScaleMode) { var gaps = new List <TimeEntryBlock>(); timeEntries.Sort((te1, te2) => te1.Started.CompareTo(te2.Started)); ulong?prevEnd = null; foreach (var entry in timeEntries) { if (prevEnd != null && entry.Started > prevEnd.Value) { var start = Toggl.DateTimeFromUnix(prevEnd.Value + 1); var block = new TimeEntryBlock(TimelineConstants.ScaleModes[selectedScaleMode]) { Started = prevEnd.Value + 1, Ended = entry.Started - 1, Height = ConvertTimeIntervalToHeight(start, Toggl.DateTimeFromUnix(entry.Started - 1), selectedScaleMode), VerticalOffset = ConvertTimeIntervalToHeight(new DateTime(start.Year, start.Month, start.Day), start, selectedScaleMode), HorizontalOffset = 0 }; if (block.Height > 10) // Don't display to small gaps not to obstruct the view { gaps.Add(block); } } prevEnd = !prevEnd.HasValue || entry.Ended > prevEnd ? entry.Ended : prevEnd; } return(gaps); }
private static List <GapTimeEntryBlock> GenerateGapTimeEntryBlocks(List <TimeEntryBlock> timeEntries) { var gaps = new List <GapTimeEntryBlock>(); TimeEntryBlock lastTimeEntry = null; foreach (var entry in timeEntries) { if (lastTimeEntry != null && entry.VerticalOffset > lastTimeEntry.Bottom) { 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 >= 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); }
public static void ChangeLastEntryStart(TimeEntryBlock item, List <TimeEntryBlock> blocks) { var(first, last) = GetOverlappingPair(item, blocks); if (last == null) { return; } Toggl.SetTimeEntryStartTimeStamp(last.TimeEntryId, (long)first.Ended); }
private void ConvertTimeEntriesToBlocks(List <Toggl.TogglTimeEntryView> timeEntries) { var timeStampsList = new List <(TimeStampType Type, TimeEntryBlock Block)>(); var blocks = new List <TimeEntryBlock>(); //The idea is to place all the starts and ends in sorted order and then assign an offset to each time entry block from the list: // - if it's a start time stamp, then pick up the minimum available offset, if none is available assign a new one. // - if it's an end time stamp, then release the offset which it occupied. foreach (var entry in timeEntries) { var startTime = Toggl.DateTimeFromUnix(entry.Started); var height = ConvertTimeIntervalToHeight(startTime, Toggl.DateTimeFromUnix(entry.Ended)); var block = new TimeEntryBlock() { Height = height < 2 ? 2 : height, VerticalOffset = ConvertTimeIntervalToHeight(new DateTime(startTime.Year, startTime.Month, startTime.Day), startTime), Color = entry.Color, Description = entry.Description, ProjectName = entry.ProjectLabel, ClientName = entry.ClientLabel, ShowDescription = true, Started = entry.Started, Ended = entry.Ended }; if (entry.Started != entry.Ended) { timeStampsList.Add((TimeStampType.Start, block)); timeStampsList.Add((TimeStampType.End, block)); } else { timeStampsList.Add((TimeStampType.Empty, block)); } blocks.Add(block); } //There can be a situation that next time entry starts exactly at the same moment, the previous one ended. //This situation must not be considered as overlap. So the comparison logic if time stamps are the same: // - always place the end time stamps first // - prefer empty time stamps to start time stamps // (otherwise if we discover a start then an empty, this will be considered as overlap, which we want to avoid) timeStampsList.Sort((te1, te2) => { var time1 = te1.Type == TimeStampType.End ? te1.Block.Ended : te1.Block.Started; var time2 = te2.Type == TimeStampType.End ? te2.Block.Ended : te2.Block.Started; var res = time1.CompareTo(time2); if (res == 0) { var getPriority = new Func <TimeStampType, int>(t => t == TimeStampType.End ? 0 : t == TimeStampType.Empty ? 1 : 2); return(getPriority(te1.Type) - getPriority(te2.Type)); } return(res); }); var offsets = new HashSet <double>(); var curOffset = 0; var usedNumOfOffsets = 0; foreach (var item in timeStampsList) { if (item.Type == TimeStampType.Start || item.Type == TimeStampType.Empty) { if (!offsets.Any()) { offsets.Add(curOffset); curOffset += 25; } if (usedNumOfOffsets > 0 || item.Block.Height < 20) { item.Block.ShowDescription = false; } item.Block.HorizontalOffset = offsets.Min(); offsets.Remove(offsets.Min()); usedNumOfOffsets++; } if (item.Type == TimeStampType.End || item.Type == TimeStampType.Empty) { offsets.Add(item.Block.HorizontalOffset); if (usedNumOfOffsets > 1 || item.Block.Height < 20) { item.Block.ShowDescription = false; } usedNumOfOffsets--; } } TimeEntryBlocks = null; TimeEntryBlocks = blocks; GenerateGapTimeEntryBlocks(timeEntries); }
private static List <TimeEntryBlock> ConvertTimeEntriesToBlocks(List <Toggl.TogglTimeEntryView> timeEntries, int selectedScaleMode, DateTime selectedDate) { var timeStampsList = new List <(TimeStampType Type, TimeEntryBlock Block)>(); var blocks = new List <TimeEntryBlock>(); //The idea is to place all the starts and ends in sorted order and then assign an offset to each time entry block from the list: // - if it's a start time stamp, then pick up the minimum available offset, if none is available assign a new one. // - if it's an end time stamp, then release the offset which it occupied. foreach (var entry in timeEntries) { var startTime = Toggl.DateTimeFromUnix(entry.Started); var height = ConvertTimeIntervalToHeight(startTime, Toggl.DateTimeFromUnix(entry.Ended), selectedScaleMode); var block = new TimeEntryBlock(entry.GUID, TimelineConstants.ScaleModes[selectedScaleMode]) { Started = entry.Started, Ended = entry.Ended, Height = Math.Max(height, TimelineConstants.MinTimeEntryBlockHeight), 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, IsResizable = height >= TimelineConstants.MinResizableTimeEntryBlockHeight }; if (entry.Started != entry.Ended) { timeStampsList.Add((TimeStampType.Start, block)); timeStampsList.Add((TimeStampType.End, block)); } else { timeStampsList.Add((TimeStampType.Empty, block)); } blocks.Add(block); } //There can be a situation that next time entry starts exactly at the same moment, the previous one ended. //This situation must not be considered as overlap. So the comparison logic if time stamps are the same: // - always place the end time stamps first // - prefer empty time stamps to start time stamps // (otherwise if we discover a start then an empty, this will be considered as overlap, which we want to avoid) timeStampsList.Sort((te1, te2) => { var time1 = te1.Type == TimeStampType.End ? te1.Block.Ended : te1.Block.Started; var time2 = te2.Type == TimeStampType.End ? te2.Block.Ended : te2.Block.Started; var res = time1.CompareTo(time2); if (res == 0) { var getPriority = new Func <TimeStampType, int>(t => t == TimeStampType.End ? 0 : t == TimeStampType.Empty ? 1 : 2); return(getPriority(te1.Type) - getPriority(te2.Type)); } return(res); }); var offsets = new HashSet <double>(); var curOffset = 0d; var usedNumOfOffsets = 0; TimeEntryBlock prevLayerBlock = null; foreach (var item in timeStampsList) { if (item.Type == TimeStampType.Start || item.Type == TimeStampType.Empty) { if (!offsets.Any()) { offsets.Add(curOffset); curOffset += TimelineConstants.TimeEntryBlockWidth + TimelineConstants.GapBetweenOverlappingTEs; } if (usedNumOfOffsets > 0 || item.Block.Height < TimelineConstants.MinShowTEDescriptionHeight) { item.Block.ShowDescription = false; if (prevLayerBlock != null) { prevLayerBlock.ShowDescription = false; } } item.Block.HorizontalOffset = offsets.Min(); offsets.Remove(offsets.Min()); usedNumOfOffsets++; prevLayerBlock = item.Block; } if (item.Type == TimeStampType.End || item.Type == TimeStampType.Empty) { offsets.Add(item.Block.HorizontalOffset); usedNumOfOffsets--; prevLayerBlock = null; } } return(blocks); }
private static (TimeEntryBlock First, TimeEntryBlock Last) GetOverlappingPair(TimeEntryBlock item, IEnumerable <TimeEntryBlock> 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); }
private static Dictionary <string, TimeEntryBlock> ConvertTimeEntriesToBlocks(List <Toggl.TogglTimeEntryView> timeEntries, Toggl.TogglTimeEntryView?runningEntry, int selectedScaleMode, DateTime selectedDate) { var timeStampsList = new List <(TimeStampType Type, TimeEntryBlock Block)>(); var blocks = new Dictionary <string, TimeEntryBlock>(); //The idea is to place all the starts and ends in sorted order and then assign an offset to each time entry block from the list: // - if it's a start time stamp, then pick up the minimum available offset, if none is available assign a new one. // - if it's an end time stamp, then release the offset which it occupied. IEnumerable <Toggl.TogglTimeEntryView> allEntries = timeEntries; if (runningEntry != null && runningEntry.Value.StartTime().Date <= selectedDate.Date && DateTime.Now.Date >= selectedDate.Date) { allEntries = allEntries.Union(new List <Toggl.TogglTimeEntryView>() { runningEntry.Value }); } foreach (var entry in allEntries) { if (blocks.ContainsKey(entry.GUID)) { continue; } var startTime = entry.StartTime(); var ended = entry.GUID == runningEntry?.GUID ? (ulong)Toggl.UnixFromDateTime(DateTime.Now) : entry.Ended; var height = ConvertTimeIntervalToHeight(startTime, Toggl.DateTimeFromUnix(ended), selectedScaleMode); var block = new TimeEntryBlock(entry, TimelineConstants.ScaleModes[selectedScaleMode], selectedDate) { Height = height, VerticalOffset = ConvertTimeIntervalToHeight(selectedDate, startTime, selectedScaleMode), IsOverlapping = false }; if (entry.Started < ended) { timeStampsList.Add((TimeStampType.Start, block)); timeStampsList.Add((TimeStampType.End, block)); } else { timeStampsList.Add((TimeStampType.Empty, block)); } blocks.Add(entry.GUID, block); } //There can be a situation that next time entry starts exactly at the same moment, the previous one ended. //This situation must not be considered as overlap. So the comparison logic if time stamps are the same: // - always place the end time stamps first // - prefer empty time stamps to start time stamps // (otherwise if we discover a start then an empty, this will be considered as overlap, which we want to avoid) timeStampsList.Sort((te1, te2) => { 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 (res.IsNearEqual(0, TimelineConstants.AcceptableBlocksOverlap)) { var getPriority = new Func <TimeStampType, int>(t => t == TimeStampType.End ? 0 : t == TimeStampType.Empty ? 1 : 2); return(getPriority(te1.Type) - getPriority(te2.Type)); } return(res < 0 ? -1 : 1); }); var offsets = new HashSet <double>(); var curOffset = 0d; var usedNumOfOffsets = 0; TimeEntryBlock prevLayerBlock = null; foreach (var item in timeStampsList) { if (item.Type == TimeStampType.Start || item.Type == TimeStampType.Empty) { if (!offsets.Any()) { offsets.Add(curOffset); curOffset += TimelineConstants.TimeEntryBlockWidth + TimelineConstants.GapBetweenOverlappingTEs; } if (usedNumOfOffsets > 0) { item.Block.IsOverlapping = true; if (prevLayerBlock != null) { prevLayerBlock.IsOverlapping = true; } } item.Block.HorizontalOffset = offsets.Min(); offsets.Remove(offsets.Min()); usedNumOfOffsets++; prevLayerBlock = item.Block; } if (item.Type == TimeStampType.End || item.Type == TimeStampType.Empty) { offsets.Add(item.Block.HorizontalOffset); usedNumOfOffsets--; prevLayerBlock = null; } } return(blocks); }