/// <summary> /// Guaranteed that data won't be null /// </summary> /// <param name="data"></param> /// <returns>Should always return an initialized list.</returns> private static async Task <List <BaseViewItemHomeworkExam> > getAllUpcomingBlocking(AccountDataItem account, AccountDataStore data, DateTime todayAsUtc, BaseUpcomingTileSettings tileSettings) { var currSemesterId = account.CurrentSemesterId; if (currSemesterId == Guid.Empty) { return(new List <BaseViewItemHomeworkExam>()); } ScheduleViewItemsGroup scheduleViewGroup; try { scheduleViewGroup = await ScheduleViewItemsGroup.LoadAsync(account.LocalAccountId, account.CurrentSemesterId, trackChanges : true, includeWeightCategories : false); } catch { // If semester not found return(new List <BaseViewItemHomeworkExam>()); } DateTime dateToStartDisplayingFrom = DateTime.SpecifyKind(tileSettings.GetDateToStartDisplayingOn(todayAsUtc), DateTimeKind.Utc); var agendaViewGroup = await AgendaViewItemsGroup.LoadAsync(account.LocalAccountId, scheduleViewGroup.Semester, DateTime.SpecifyKind(todayAsUtc, DateTimeKind.Local), trackChanges : true); // We're not going to worry about locking changes while we enumerate, since if collection changes while we're enumerating, there'll be a // new incoming reset request anyways // Agenda view group doesn't sort, so we have to sort it return(agendaViewGroup.Items.Where( i => i.Date.Date >= dateToStartDisplayingFrom && ((tileSettings.ShowHomework && i is ViewItemHomework) || (tileSettings.ShowExams && i is ViewItemExam)) ).OrderBy(i => i).ToList()); }
protected override async Task LoadAsyncOverride() { AgendaViewItemsGroup = await AgendaViewItemsGroup.LoadAsync(MainScreenViewModel.CurrentLocalAccountId, MainScreenViewModel.CurrentSemester, Today); AgendaViewItemsGroup.Items.CollectionChanged += new WeakEventHandler <NotifyCollectionChangedEventArgs>(Items_CollectionChanged).Handler; UpdateHasNoItems(); ListenToLocalEditsFor <DataLayer.DataItems.DataItemMegaItem>().ChangedItems += ItemsLocallyEditedListener_ChangedItems; }
private static async Task <AgendaViewItemsGroup> GetAgendaViewItemsGroup(AccountDataItem account, DateTime today) { try { // We're getting cached versions which might change as we're using the app, but that's fine, since if items change, another // ResetReminders will be incoming anyways var scheduleViewItemsGroup = await ScheduleViewItemsGroup.LoadAsync(account.LocalAccountId, account.CurrentSemesterId, trackChanges : true, includeWeightCategories : false); return(await AgendaViewItemsGroup.LoadAsync(account.LocalAccountId, scheduleViewItemsGroup.Semester, today, trackChanges : true)); } // If semester didn't exist, it throws null reference exception catch { return(null); } }
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)); } } }
private async System.Threading.Tasks.Task InitializeDataAsync() { List <object> items = null; List <BaseViewItemHomeworkExam> tasks = null; _now = DateTime.Now; bool hasAccount = false; bool isDisabledInSettings = false; bool hasSemester = false; try { await System.Threading.Tasks.Task.Run(async delegate { var account = await AccountsManager.GetLastLogin(); AccountDataStore data = null; if (account != null) { data = await AccountDataStore.Get(account.LocalAccountId); } if (data != null) { hasAccount = true; isDisabledInSettings = account.MainTileSettings.IsDisabled(); if (!isDisabledInSettings) { _localAccountId = account.LocalAccountId; var currSemesterId = account.CurrentSemesterId; if (currSemesterId != Guid.Empty) { ScheduleViewItemsGroup scheduleViewGroup; try { scheduleViewGroup = await ScheduleViewItemsGroup.LoadAsync(account.LocalAccountId, account.CurrentSemesterId, trackChanges: true, includeWeightCategories: false); } catch { // If semester not found scheduleViewGroup = null; } if (scheduleViewGroup != null) { DateTime dateToStartDisplayingFrom = DateTime.SpecifyKind(account.MainTileSettings.GetDateToStartDisplayingOn(_now.Date), DateTimeKind.Utc); // We don't track changes since we need a fresh version that has been filtered right now. // Otherwise when we update for an event expiring, if we have a cached version, that event wouldn't have expired! var agendaViewGroup = await AgendaViewItemsGroup.LoadAsync(account.LocalAccountId, scheduleViewGroup.Semester, _now.Date, trackChanges: false); hasSemester = true; // We lock the outside, since we are allowing trackChanges on the view items groups (so we have a chance of loading a cached one)... and since we're on a background thread, the lists inside the // view items groups could change while we're enumerating, hence throwing an exception. So we lock it to ensure this won't happen, and then we return a copy of the items that we need. using (await agendaViewGroup.DataChangeLock.LockForReadAsync()) { var filtered = agendaViewGroup.Items .Where(i => i.Date.Date >= dateToStartDisplayingFrom); if (!account.MainTileSettings.ShowHomework) { filtered = filtered.Where(i => !(i is ViewItemHomework)); } if (!account.MainTileSettings.ShowExams) { filtered = filtered.Where(i => !(i is ViewItemExam)); } // Agenda view group doesn't sort, so we have to sort it tasks = filtered .OrderBy(i => i) .Take(20) .ToList(); // Add date headers items = new List <object>(); DateTime lastHeader = DateTime.MinValue; foreach (var t in tasks) { if (lastHeader != t.Date.Date) { items.Add(t.Date.Date); lastHeader = t.Date.Date; } items.Add(t); } } } } } } }); if (items == null || items.Count == 0) { if (hasSemester) { items = new List <object>() { PowerPlannerResources.GetString("String_NothingDue") }; } else if (isDisabledInSettings) { items = new List <object>() { PowerPlannerResources.GetString("String_WidgetDisabled") }; } else if (hasAccount) { items = new List <object>() { PowerPlannerResources.GetString("String_NoSemester") }; } else { items = new List <object>() { PowerPlannerResources.GetString("String_NoAccount") }; } } _items = items; } catch (Exception ex) { TelemetryExtension.Current?.TrackException(ex); } // Schedule next try { if (tasks != null && tasks.Count > 0) { DateTime?nextChangeTime = GetNextAgendaChangeTime(tasks, _now); if (nextChangeTime != null) { AlarmManagerHelper.Schedule( context: _context, receiverType: typeof(UpdateWidgetAgendaReceiver), wakeTime: nextChangeTime.Value); } } } catch (Exception ex) { TelemetryExtension.Current?.TrackException(ex); } }