From 6306bb697fe05ee19e05d5a1aea6d3417a8c802b Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 20 Apr 2022 14:26:22 +0200 Subject: [PATCH] MOBILE-4042 calendar: Swipe navigation in event --- src/addons/calendar/classes/events-source.ts | 64 +++++++++++++++++++ src/addons/calendar/pages/day/day.html | 2 +- src/addons/calendar/pages/day/day.page.ts | 41 +++++++++++- src/addons/calendar/pages/event/event.html | 2 +- src/addons/calendar/pages/event/event.page.ts | 45 ++++++++++++- .../routed-items-manager-sources-tracker.ts | 24 ++++++- 6 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 src/addons/calendar/classes/events-source.ts diff --git a/src/addons/calendar/classes/events-source.ts b/src/addons/calendar/classes/events-source.ts new file mode 100644 index 000000000..92decbee5 --- /dev/null +++ b/src/addons/calendar/classes/events-source.ts @@ -0,0 +1,64 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { AddonCalendarEventToDisplay } from '@addons/calendar/services/calendar'; +import { Params } from '@angular/router'; +import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source'; + +/** + * Provides a collection of calendar events. + */ +export class AddonCalendarEventsSource extends CoreRoutedItemsManagerSource { + + readonly DATE: string; + + private events: AddonCalendarEventToDisplay[] = []; + + constructor(date: string) { + super(); + + this.DATE = date; + } + + /** + * Set events. + * + * @param events Events. + */ + setEvents(events: AddonCalendarEventToDisplay[]): void { + this.events = events; + } + + /** + * @inheritdoc + */ + protected async loadPageItems(): Promise<{ items: AddonCalendarEventToDisplay[] }> { + return { items: this.events.slice(0) }; + } + + /** + * @inheritdoc + */ + getItemPath(event: AddonCalendarEventToDisplay): string { + return event.id.toString(); + } + + /** + * @inheritdoc + */ + getItemQueryParams(): Params { + return { date: this.DATE }; + } + +} diff --git a/src/addons/calendar/pages/day/day.html b/src/addons/calendar/pages/day/day.html index fba17f45a..e126fbfc2 100644 --- a/src/addons/calendar/pages/day/day.html +++ b/src/addons/calendar/pages/day/day.html @@ -67,7 +67,7 @@ diff --git a/src/addons/calendar/pages/day/day.page.ts b/src/addons/calendar/pages/day/day.page.ts index 36746c630..f8e013549 100644 --- a/src/addons/calendar/pages/day/day.page.ts +++ b/src/addons/calendar/pages/day/day.page.ts @@ -45,6 +45,8 @@ import { CoreSwipeSlidesDynamicItem, CoreSwipeSlidesDynamicItemsManagerSource, } from '@classes/items-management/swipe-slides-dynamic-items-manager-source'; +import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; +import { AddonCalendarEventsSource } from '@addons/calendar/classes/events-source'; /** * Page that displays the calendar events for a certain day. @@ -342,9 +344,10 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { * Navigate to a particular event. * * @param eventId Event to load. + * @param day Day. */ - gotoEvent(eventId: number): void { - CoreNavigator.navigateToSitePath(`/calendar/event/${eventId}`); + gotoEvent(eventId: number, day: PreloadedDay): void { + CoreNavigator.navigateToSitePath(`/calendar/event/${eventId}`, { params: { date: day.moment.format('MMDDY') } }); } /** @@ -452,6 +455,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { this.manualSyncObserver?.off(); this.onlineObserver?.unsubscribe(); this.filterChangedObserver?.off(); + this.manager?.getSource().forgetRelatedSources(); this.manager?.destroy(); this.managerUnsubscribe && this.managerUnsubscribe(); } @@ -483,6 +487,7 @@ type PreloadedDay = DayBasicData & CoreSwipeSlidesDynamicItem & { class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicItemsManagerSource { courses: Partial[] = []; + eventsSources: Set = new Set(); // Offline events classified in month & day. offlineEvents: Record> = {}; offlineEditedEventsIds: number[] = []; // IDs of events edited in offline. @@ -533,6 +538,8 @@ class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicIte */ filterEvents(day: PreloadedDay, filter: AddonCalendarFilter): void { day.filteredEvents = AddonCalendarHelper.getFilteredEvents(day.events || [], filter, this.categories || {}); + + this.rememberEventsList(day); } /** @@ -816,4 +823,34 @@ class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicIte } } + /** + * Forget other sources that where created whilst using this one. + */ + forgetRelatedSources(): void { + for (const source of this.eventsSources) { + CoreRoutedItemsManagerSourcesTracker.removeReference(source, this); + } + } + + /** + * Remember the list of events in a day to be used in a different context. + * + * @param day Day containing the events list. + */ + private async rememberEventsList(day: PreloadedDay): Promise { + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCalendarEventsSource, [ + day.moment.format('MMDDY'), + ]); + + if (!this.eventsSources.has(source)) { + this.eventsSources.add(source); + + CoreRoutedItemsManagerSourcesTracker.addReference(source, this); + } + + source.setEvents(day.filteredEvents ?? []); + + await source.reload(); + } + } diff --git a/src/addons/calendar/pages/event/event.html b/src/addons/calendar/pages/event/event.html index ab3c5bcb8..5180e04a9 100644 --- a/src/addons/calendar/pages/event/event.html +++ b/src/addons/calendar/pages/event/event.html @@ -26,7 +26,7 @@ - + diff --git a/src/addons/calendar/pages/event/event.page.ts b/src/addons/calendar/pages/event/event.page.ts index 28c5de7e9..e032a0247 100644 --- a/src/addons/calendar/pages/event/event.page.ts +++ b/src/addons/calendar/pages/event/event.page.ts @@ -36,9 +36,12 @@ import { Network, NgZone, Translate } from '@singletons'; import { Subscription } from 'rxjs'; import { CoreNavigator } from '@services/navigator'; import { CoreUtils } from '@services/utils/utils'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; import { CoreConstants } from '@/core/constants'; import { AddonCalendarReminderTimeModalComponent } from '@addons/calendar/components/reminder-time-modal/reminder-time-modal'; +import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; +import { AddonCalendarEventsSource } from '@addons/calendar/classes/events-source'; +import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager'; /** * Page that displays a single calendar event. @@ -63,6 +66,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { eventLoaded = false; event?: AddonCalendarEventToDisplay; + events?: CoreSwipeNavigationItemsManager; courseId?: number; courseName = ''; groupName?: string; @@ -155,7 +159,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { /** * View loaded. */ - ngOnInit(): void { + async ngOnInit(): Promise { try { this.eventId = CoreNavigator.getRequiredRouteNumberParam('id'); } catch (error) { @@ -168,7 +172,8 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { this.syncIcon = CoreConstants.ICON_LOADING; - this.fetchEvent(); + await this.initializeSwipeManager(); + await this.fetchEvent(); } /** @@ -292,6 +297,25 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { this.syncIcon = CoreConstants.ICON_SYNC; } + /** + * Initialize swipe manager if enabled. + */ + protected async initializeSwipeManager(): Promise { + const date = CoreNavigator.getRouteParam('date'); + const source = date && CoreRoutedItemsManagerSourcesTracker.getSource( + AddonCalendarEventsSource, + [date], + ); + + if (!source) { + return; + } + + this.events = new AddonCalendarEventsSwipeItemsManager(source); + + await this.events.start(); + } + /** * Sync offline events. * @@ -620,7 +644,22 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { this.manualSyncObserver.off(); this.onlineObserver.unsubscribe(); this.newEventObserver.off(); + this.events?.destroy(); clearInterval(this.updateCurrentTime); } } + +/** + * Helper to manage swiping within a collection of events. + */ +class AddonCalendarEventsSwipeItemsManager extends CoreSwipeNavigationItemsManager { + + /** + * @inheritdoc + */ + protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null { + return route.params.id; + } + +} diff --git a/src/core/classes/items-management/routed-items-manager-sources-tracker.ts b/src/core/classes/items-management/routed-items-manager-sources-tracker.ts index 6d0e0b740..539ed93e2 100644 --- a/src/core/classes/items-management/routed-items-manager-sources-tracker.ts +++ b/src/core/classes/items-management/routed-items-manager-sources-tracker.ts @@ -30,6 +30,26 @@ export class CoreRoutedItemsManagerSourcesTracker { private static instances: WeakMap = new WeakMap(); private static instanceIds: WeakMap = new WeakMap(); + /** + * Retrieve an instance given the constructor arguments or id. + * + * @param constructor Source constructor. + * @param constructorArgumentsOrId Arguments to create a new instance, or the id if it's known. + * @returns Source. + */ + static getSource>( + constructor: C, + constructorArgumentsOrId: ConstructorParameters | string, + ): SourceConstuctorInstance | null { + const id = typeof constructorArgumentsOrId === 'string' + ? constructorArgumentsOrId + : constructor.getSourceId(...constructorArgumentsOrId); + const constructorInstances = this.getConstructorInstances(constructor); + + return constructorInstances[id]?.instance as SourceConstuctorInstance + ?? null; + } + /** * Create an instance of the given source or retrieve one if it's already in use. * @@ -42,9 +62,9 @@ export class CoreRoutedItemsManagerSourcesTracker { constructorArguments: ConstructorParameters, ): SourceConstuctorInstance { const id = constructor.getSourceId(...constructorArguments); - const constructorInstances = this.getConstructorInstances(constructor); - return constructorInstances[id]?.instance as SourceConstuctorInstance + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return this.getSource(constructor, id) as any ?? this.createInstance(id, constructor, constructorArguments); }