private static ObservableCollection<EventsForDay> RemoveEventFromGroup(Event ev, ObservableCollection<EventsForDay> eventsByDay)
        {
            foreach (var group in eventsByDay)
            {
                group.Remove(ev);
            }

            return new ObservableCollection<EventsForDay>(eventsByDay.Where(group => group.Count > 0));
        }
 private async void DeleteEvent(Event ev)
 {
     try
     {
         await DataStore.RemoveEvent(ev);
         Events.Remove(ev);
     }
     catch (Exception ex)
     {
         ReportError(ex);
     }
 }
        private static void AddEventToGroup(Event ev, ObservableCollection<EventsForDay> eventsByDay)
        {
            for (var date = ev.Start; date < ev.End; date = date.AddDays(1))
            {
                var day = eventsByDay.FirstOrDefault(d => d.Date == date);

                if (day != null)
                {
                    day.Add(ev);
                }
                else
                {
                    day = new EventsForDay { Date = date };
                    day.Add(ev);

                    // Insert in chronological order (rather than following each add with an OrderBy...)
                    //
                    int index = eventsByDay.IndexOf(eventsByDay.FirstOrDefault(e => e.Date > day.Date));
                    if (index < 0)
                    {
                        index = eventsByDay.Count;
                    }
                    eventsByDay.Insert(index, day);
                }
            }
        }
Пример #4
0
        /// <summary>
        /// Create one or more labels for an event and place them on the grid.
        /// </summary>
        /// <param name="ev">Event to place</param>
        /// <returns>The placed labels</returns>
        private List<DraggableView> PlaceEvent(Event ev)
        {
            var labels = new List<DraggableView>();

            var offset = (ev.StartDate - Start.Date).Days;
            var row = (int)Math.Truncate((double)offset / _cols);

            var daySpan = ev.DaySpan;
            var rowSpan = (int)Math.Ceiling((double)(offset + daySpan) / _cols);

            // Truncate events that actually start before the selected start date
            //
            bool truncatedStart = false;

            if (offset < 0)
            {
                daySpan += offset;
                offset = 0;
                truncatedStart = true;
            }

            var col = offset % _cols;
            var colSpan = 0;

            System.Diagnostics.Debug.WriteLine($"{ev.Name} has a day span of {daySpan}");

            for (int day = 0; day < daySpan && row < _rows; day += colSpan, row++)
            {
                colSpan = Math.Min(daySpan - day, _cols - col);

                // Margin is adjusted so labels that span rows appear continuous
                //
                var margin = new Thickness(day == 0 && !truncatedStart ? _defaultMargin : 0,
                    _defaultMargin,
                    (day + colSpan < daySpan) ? 0 : _defaultMargin,
                    _defaultMargin);

                labels.Add(PlaceEventLabel(ev, row, col, colSpan, margin));

                col = 0; // only the first row has a column offset
            }

            return labels;
        }
        private async void EditEvent(Event ev)
        {
            try
            {
                _editingEvent = ev;

                var eventVM = await Navigator.PushModalAndWaitAsync<EventEditorViewModel>(vm => vm.Event = ev);

                if (eventVM.Result == ModalResult.Deleted)
                {
                    await DataStore.RemoveEvent(ev);
                    Events.Remove(ev);
                }
                else if (eventVM.Result != ModalResult.Canceled)
                {
                    await DataStore.UpdateEvent(ev);
                }
            }
            catch (Exception ex)
            {
                ReportError(ex);
            }

            _editingEvent = null;
        }
Пример #6
0
        /// <summary>
        /// Remove resize mode views
        /// </summary>
        /// <param name="overlay">The resize mode overlay that triggered this, so we can remove it</param>
        private void EndResizeMode(View overlay)
        {
            _grid.Children.Remove(overlay);
            _grid.Children.Remove(_rightResizeHandle);
            _grid.Children.Remove(_rightResizeHandleDragger);
            _grid.Children.Remove(_leftResizeHandle);
            _grid.Children.Remove(_leftResizeHandleDragger);

            ((TappableView)overlay).Tapped -= HandleOverlayTapped;
            _rightResizeHandleDragger.Dragging -= HandleResizeDragging;
            _rightResizeHandleDragger.Dragged -= HandleResizeDragged;
            _rightResizeHandleDragger.DragCanceled -= HandleResizeCanceled;
            _leftResizeHandleDragger.Dragging -= HandleResizeDragging;
            _leftResizeHandleDragger.Dragged -= HandleResizeDragged;
            _leftResizeHandleDragger.DragCanceled -= HandleResizeCanceled;

            // Reset opacity
            foreach (var label in _labels)
            {
                label.Opacity = 1.0;
            }

            UnhighlightCells();

            _resizingEvent = null;
            _rightResizeHandle = null;
            _rightResizeHandleDragger = null;
            _leftResizeHandle = null;
            _leftResizeHandleDragger = null;
            _resizeRange = null;
        }
Пример #7
0
 /// <summary>
 /// Moves an event to a new start date (preserving duration) and updates labels.
 /// </summary>
 /// <param name="ev">Event to move</param>
 /// <param name="newStartDate">New start date for event</param>
 private void MoveEvent(Event ev, DateTime newStartDate)
 {
     var newRange = new TimeRange(ev.Start, ev.End);
     newRange.MoveToDate(newStartDate);
     UpdateEvent(ev, newRange);
 }
Пример #8
0
        /// <summary>
        /// Applies the new range to the event and updates its label(s)
        /// </summary>
        /// <param name="ev">Event to update</param>
        /// <param name="newRange">Desired time range for the event</param>
        private void UpdateEvent(Event ev, TimeRange newRange)
        {
            // Q: Why don't we just update the properties on the Event and let the property change events handle this?
            // A: Mainly to avoid having to deal with multiple notifications firing for a single change (e.g., moving an event)
            //    Updating the calendar labels is a bit more expensive than just updating a text box.

            if (newRange.Start == ev.Start && newRange.End == ev.End)
            {
                // no change
                return;
            }

            var newCells = GetCellsForRange(newRange.StartDate, newRange.EndDate);

            if (!newCells.Any())
            {
                // Don't allow moving off the calendar entirely
                //
                return;
            }

            _updatingEvent = true;
            ev.Start = newRange.Start;
            ev.End = newRange.End;
            _updatingEvent = false;

            var labels = _labels.Where(l => l.BindingContext == ev).ToList();

            System.Diagnostics.Debug.Assert(labels.Any());

            var label = labels.First();

            bool wasMultiRow = labels.Count > 1;

            var newRows = newCells.Select(cell => Grid.GetRow(cell)).Distinct().ToList();

            bool willBeMultiRow = newRows.Count > 1;

            // If the event fit on a single row before and will still fit on a single row,
            // then we can "simply" move the existing label. Otherwise, we need to recreate,
            // because multiple labels are involved with changing widths etc.
            //
            if (!wasMultiRow && !willBeMultiRow && GetEventContainerForRow(newRows.First()) == label.ParentView)
            {
                var container = label.ParentView as AutoStackGrid;

                // So basically all the previous complexity is being turned into:
                // AutoStackGrids span whole week (row). If the event is being moved
                // within a row, we just reposition it in the stack grid. Otherwise
                // re-create.

                // Note that even with the stack-per-row model, moving items between
                // layout containers is problematic on WinPhone (hence re-creating instead)

                var newCols = newCells.Select(cell => Grid.GetColumn(cell)).OrderBy(col => col).ToList();

                // Defer layout until we're done updating properties
                //
                container.DeferLayout = true;

                AutoStackGrid.SetColumn(label, newCols.First());
                AutoStackGrid.SetColumnSpan(label, newCols.Last() - newCols.First() + 1);

                // Update margin. Even though we're staying on one row, there's the possibility that
                // the event actually stretches before and/or after that row and those parts just aren't visible
                //
                bool truncatedStart = ev.Start < (newCells.First().BindingContext as DateTime?).Value;

                // Subtracting 1 second because an end time of midnight is exclusive and should only
                // label the previous day
                //
                bool truncatedEnd = ev.End.AddSeconds(-1).Date > (newCells.Last().BindingContext as DateTime?).Value;

                var margin = new Thickness(!truncatedStart ? _defaultMargin : 0,
                    _defaultMargin,
                    !truncatedEnd ? _defaultMargin : 0,
                    _defaultMargin);

                AutoStackGrid.SetMargin(label, margin);

                container.DeferLayout = false;
                container.ForceLayout();
            }
            else
            {
                // Recreate labels

                // Removing a label just to create a new one in its place caused movement flicker
                // on WinPhone if we rely on adding/removing to automatically update layout.
                // So that behavior is temporarily disabled on AutoStackGrid, and instead we
                // explicitly call ForceLayout on grids that have been modified.
                //
                var oldContainers = labels.Select(l => l.ParentView).OfType<AutoStackGrid>().ToList();

                foreach (var container in oldContainers)
                {
                    container.DeferLayout = true;
                }

                ClearEventLabels(labels);
                _labels = _labels.Except(labels).ToList();
                var newLabels = PlaceEvent(ev);

                foreach (var container in oldContainers.Union(newLabels.Select(l => l.ParentView)).OfType<AutoStackGrid>())
                {
                    container.ForceLayout();
                    container.DeferLayout = false;
                }
            }
        }
Пример #9
0
        /// <summary>
        /// Creates overlay and tracking handles, highlights cells, and dims other events.
        /// </summary>
        /// <param name="ev">Event to resize</param>
        private void BeginResizeMode(Event ev)
        {
            _resizingEvent = ev;

            _resizeRange = new TimeRange(ev.Start, ev.End);

            // Create overlay to capture taps for dismissing resize mode
            //
            var overlay = new TappableView();
            Grid.SetRowSpan(overlay, _rows + 1);
            Grid.SetColumnSpan(overlay, _cols);
            overlay.BackgroundColor = Color.Transparent;

            overlay.Tapped += HandleOverlayTapped;

            _grid.Children.Add(overlay);

            // Dim everything but this event
            //
            foreach (var label in _labels.Where(l => l.BindingContext != _resizingEvent))
            {
                label.Opacity = 0.5;
            }

            HighlightCells(_resizingEvent.StartDate, _resizingEvent.EndDate);

            var cells = GetCellsForRange(_resizingEvent.StartDate, _resizingEvent.EndDate);
            var firstCell = cells.First();
            var lastCell = cells.Last();

            _leftResizeHandle = CreateResizeHandleVisual(Grid.GetColumn(firstCell), Grid.GetRow(firstCell), false);
            _grid.Children.Add(_leftResizeHandle);
            _rightResizeHandle = CreateResizeHandleVisual(Grid.GetColumn(lastCell), Grid.GetRow(lastCell), true);
            _grid.Children.Add(_rightResizeHandle);

            _leftResizeHandleDragger = CreateResizeHandleDraggerForVisual(_leftResizeHandle);
            _grid.Children.Add(_leftResizeHandleDragger);
            _rightResizeHandleDragger = CreateResizeHandleDraggerForVisual(_rightResizeHandle);
            _grid.Children.Add(_rightResizeHandleDragger);
        }
Пример #10
0
        /// <summary>
        /// Create an individual label corresponding to an event (possibly only part of the
        /// event) and place it on the grid.
        /// </summary>
        private DraggableView PlaceEventLabel(Event ev, int row, int col, int colSpan, Thickness margin = default(Thickness))
        {
            System.Diagnostics.Debug.WriteLine($"Placing {ev.Name} with a span of {colSpan}");

            var view = new DraggableView { BackgroundColor = Color.Blue, VerticalOptions = LayoutOptions.FillAndExpand };
            view.BindingContext = ev;

            var label = new Label();

            label.SetBinding<Event>(Label.TextProperty, e => e.Name);

            label.VerticalOptions = LayoutOptions.Fill;
            label.YAlign = TextAlignment.Center;
            label.FontSize = Device.GetNamedSize(NamedSize.Small, view);
            label.HorizontalOptions = LayoutOptions.FillAndExpand;

            view.Content = label;

            AutoStackGrid.SetMargin(view, margin);

            var container = GetEventContainerForRow(row + 1);

            container.AddChild(view, col, colSpan);

            var tapGesture = new TapGestureRecognizer();
            // First binding is to the "parent" context (i.e., ours, as opposed to the label's)
            //tapGesture.SetBinding(TapGestureRecognizer.CommandProperty, new Binding("EditEventCommand", BindingMode.Default, null, null, null, _vm));
            //tapGesture.SetBinding(TapGestureRecognizer.CommandParameterProperty, new Binding("."));

            // Activates resize mode instead of opening editor
            //
            tapGesture.Tapped += HandleEventTapped;

            view.GestureRecognizers.Add(tapGesture);

            view.Dragged += HandleDragged;
            view.DragStart += HandleDragStart;
            view.Dragging += HandleDragging;
            view.DragCanceled += HandleDragCanceled;

            _labels.Add(view);

            return view;
        }
Пример #11
0
        /// <summary>
        /// Sets the opacity for all labels corresponding to a specified Event.
        /// </summary>
        /// <param name="ev">Event to set opacity for</param>
        /// <param name="opacity">Desired opacity</param>
        private void SetEventOpacity(Event ev, double opacity)
        {
            var labels = _labels.Where(l => l.BindingContext == ev).ToList();

            foreach (var label in labels)
            {
                label.Opacity = opacity;
            }
        }
        public async void EventEditor_SettingEvent_UpdatesProperties()
        {
            var ev = new Event { Name = "Bob" };

            await _vm.WaitForPropertyChangeAsync(() => _vm.Name, () =>
            {
                _vm.Event = ev;
            });

            Assert.AreEqual(ev.Name, _vm.Name);
        }
Пример #13
0
 public Task UpdateEvent(Event ev)
 {
     return Connection.UpdateAsync(ev);
 }
Пример #14
0
 public Task RemoveEvent(Event ev)
 {
     return Connection.DeleteAsync(ev);
 }
Пример #15
0
 public Task AddEvent(Event ev)
 {
     return Connection.InsertAsync(ev);
 }
        protected override void Done()
        {
            if (_end <= _start)
            {
                ReportMessage("Start must precede End", "Time travel is not supported");
                return;
            }

            Result = ModalResult.Done;

            if (_event == null)
            {
                _event = new Event { AllDay = true };
            }

            _event.Name = Name;
            _event.Start = Start;
            _event.End = End;

            Navigator.PopModalAsync();
        }