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); } } }
/// <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; }
/// <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; }
/// <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); }
/// <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; } } }
/// <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); }
/// <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; }
/// <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); }
public Task UpdateEvent(Event ev) { return Connection.UpdateAsync(ev); }
public Task RemoveEvent(Event ev) { return Connection.DeleteAsync(ev); }
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(); }