private async Task ResetReminders(AccountDataItem account, CancellationToken token) { if (account == null) { return; } ClearReminders(account.LocalAccountId, token); token.ThrowIfCancellationRequested(); var semesterId = account.CurrentSemesterId; if (semesterId == Guid.Empty) { return; } DateTime todayAsUtc = DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc); ViewItemTaskOrEvent[] itemsDueTodayOrGreater; ViewItemSchedule[] allSchedules; try { ScheduleViewItemsGroup viewModelSchedule = await ScheduleViewItemsGroup.LoadAsync(account.LocalAccountId, semesterId, trackChanges : true, includeWeightCategories : false); AgendaViewItemsGroup viewModel = await AgendaViewItemsGroup.LoadAsync(account.LocalAccountId, viewModelSchedule.Semester, DateTime.SpecifyKind(todayAsUtc, DateTimeKind.Local), trackChanges : true); // Data we need to load and hold are... // - Items due today or greater // - All schedules // We don't need to worry about holding a lock though, if the collections change and that breaks us, that's fine, // that implies that the data has been changed anyways and another ResetReminders will come in. // Therefore we should also expect to get some exceptions here. allSchedules = viewModelSchedule.Classes.SelectMany(i => i.Schedules).ToArray(); itemsDueTodayOrGreater = viewModel.Items.Where(i => i.Date.Date >= DateTime.SpecifyKind(todayAsUtc, DateTimeKind.Local)).ToArray(); } catch { // Semester wasn't found or other misc error return; } // Since we're no longer inside the lock and we're using a view group that tracks changes, any properties we access on the view items could change at any time. // Therefore we need to take that into consideration and be careful about what we do. if (account.RemindersDayBefore) { Dictionary <DateTime, List <ViewItemTaskOrEvent> > groupedByDay = new Dictionary <DateTime, List <ViewItemTaskOrEvent> >(); DateTime tomorrow = DateTime.SpecifyKind(todayAsUtc.AddDays(1), DateTimeKind.Local); //select all incomplete tasks/events that is due on tomorrow or later foreach (ViewItemTaskOrEvent h in itemsDueTodayOrGreater.Where(i => i.Date.Date >= tomorrow)) { token.ThrowIfCancellationRequested(); var hDate = h.Date.Date; if (!groupedByDay.TryGetValue(hDate, out List <ViewItemTaskOrEvent> group)) { group = new List <ViewItemTaskOrEvent>(); groupedByDay[hDate] = group; } group.Add(h); } foreach (var pair in groupedByDay) { token.ThrowIfCancellationRequested(); DateTime dueOn = pair.Key; List <ViewItemTaskOrEvent> items = pair.Value; DateTime reminderTime = GetDayBeforeReminderTime(dueOn.AddDays(-1), account, allSchedules); if (!IsTimeOkay(reminderTime)) { continue; } ViewItemTaskOrEvent[] tasks = items.Where(i => i.Type == TaskOrEventType.Task).ToArray(); ViewItemTaskOrEvent[] events = items.Where(i => i.Type == TaskOrEventType.Event).ToArray(); if (tasks.Length > 0) { XmlDocument xml = GenerateToastReminder( tasks.Length == 1 ? "You have 1 item due tomorrow" : "You have " + tasks.Length + " items due tomorrow", GetItemLineText(tasks[0]), tasks.Length >= 2 ? GetItemLineText(tasks[1]) : null, #pragma warning disable 0612 new QueryStringHelper() #pragma warning restore 0612 .SetLocalAccountId(account.LocalAccountId) .SetAction("DayBeforeHomeworkReminder") .ToString() ); string remoteId = null; if (account.IsOnlineAccount) { int hashedItems = string.Join(";", tasks.Select(i => i.Identifier)).GetHashCode(); remoteId = $"PP_DayBeforeHomeworks_{account.AccountId}_{hashedItems}"; } Schedule( GenerateScheduledToastNotification( xml, reminderTime, GetId(account), //id's don't need to be unique remoteId) ); } if (events.Length > 0) { XmlDocument xml = GenerateToastReminder( events.Length == 1 ? "You have 1 event tomorrow" : "You have " + events.Length + " events tomorrow", GetItemLineText(events[0]), events.Length >= 2 ? GetItemLineText(events[1]) : null, #pragma warning disable 0612 new QueryStringHelper() #pragma warning restore 0612 .SetLocalAccountId(account.LocalAccountId) .SetAction("DayBeforeExamReminder") .ToString() ); string remoteId = null; if (account.IsOnlineAccount) { int hashedItems = string.Join(";", events.Select(i => i.Identifier)).GetHashCode(); remoteId = $"PP_DayBeforeExams_{account.AccountId}_{hashedItems}"; } Schedule( GenerateScheduledToastNotification( xml, reminderTime, GetId(account), remoteId)); } } } if (account.RemindersDayOf) { foreach (ViewItemTaskOrEvent h in itemsDueTodayOrGreater) { token.ThrowIfCancellationRequested(); bool hasClassTime = false; DateTime reminderTime = GetDayOfReminderTime(h, ref hasClassTime); if (!IsTimeOkay(reminderTime)) { continue; } string subtitle = GetClassName(h) + " - "; if (h.Type == TaskOrEventType.Task) { subtitle += "due "; } if (hasClassTime) { subtitle += "in one hour"; } else { subtitle += "today"; } XmlDocument xml = GenerateToastReminder( TrimString(h.Name, 200), subtitle, TrimString(h.Details, 200), ArgumentsHelper.CreateArgumentsForView(h, account.LocalAccountId).SerializeToString() ); string remoteId = null; if (account.IsOnlineAccount) { remoteId = $"PP_DayOf_{account.AccountId}_{h.Identifier}"; } Schedule( GenerateScheduledToastNotification( xml, reminderTime, GetId(account), remoteId)); } } }
public RemoteViews GetViewAt(int position) { try { object item = _items[position]; if (item is DateTime) { var dateHeaderView = new RemoteViews(_context.PackageName, Resource.Layout.WidgetAgendaDateListItem); dateHeaderView.SetTextViewText(Resource.Id.WidgetAgendaDateHeaderTextView, ToFriendlyDate((DateTime)item, _now)); return(dateHeaderView); } if (item is string) { // String represents "Nothing due!", etc var emptyView = new RemoteViews(_context.PackageName, Resource.Layout.WidgetAgendaEmptyListItem); emptyView.SetTextViewText(Resource.Id.WidgetAgendaEmptyListItemTextView, item as string); return(emptyView); } var task = item as BaseViewItemHomeworkExam; var c = task.GetClassOrNull(); if (c == null) { var emptyView = new RemoteViews(_context.PackageName, Resource.Layout.WidgetAgendaEmptyListItem); return(emptyView); } RemoteViews taskView = new RemoteViews(_context.PackageName, Resource.Layout.WidgetAgendaTaskListItem); taskView.SetTextViewText(Resource.Id.WidgetAgendaTaskTextView, task.Name); taskView.SetInt(Resource.Id.WidgetAgendaTaskColorBar, "setBackgroundColor", ColorTools.GetColor(c.Color)); Intent taskIntent = new Intent() .SetAction(Intent.ActionView) .SetData(Android.Net.Uri.Parse("powerplanner:?" + ArgumentsHelper.CreateArgumentsForView(task, _localAccountId).SerializeToString())); taskView.SetOnClickFillInIntent(Resource.Id.WidgetAgendaTaskListItem, taskIntent); return(taskView); } catch (Exception ex) { // Out of range exception can be expected since the items list can change while we're working if (!(ex is ArgumentOutOfRangeException)) { TelemetryExtension.Current?.TrackException(ex); } var emptyView = new RemoteViews(_context.PackageName, Resource.Layout.WidgetAgendaEmptyListItem); return(emptyView); } }