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);
        }
Ejemplo n.º 2
0
        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);
        }
Ejemplo n.º 3
0
 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);
 }
Ejemplo n.º 4
0
        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);
        }
Ejemplo n.º 6
0
        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);
        }
Ejemplo n.º 7
0
        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);
        }