Merge pull request #3302 from NoelDeMartin/MOBILE-4042

MOBILE-4042 calendar: Swipe navigation in event
main
Dani Palou 2022-06-02 09:23:25 +02:00 committed by GitHub
commit 11c1b2a7da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 169 additions and 9 deletions

View File

@ -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<AddonCalendarEventToDisplay> {
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 };
}
}

View File

@ -67,7 +67,7 @@
<ng-container *ngFor="let event of day.filteredEvents"> <ng-container *ngFor="let event of day.filteredEvents">
<ion-card> <ion-card>
<ion-item class="addon-calendar-event ion-text-wrap" [attr.aria-label]="event.name" <ion-item class="addon-calendar-event ion-text-wrap" [attr.aria-label]="event.name"
(click)="gotoEvent(event.id)" [class.item-dimmed]="event.ispast" (click)="gotoEvent(event.id, day)" [class.item-dimmed]="event.ispast"
[ngClass]="['addon-calendar-eventtype-'+event.eventtype]" button [detail]="false"> [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" button [detail]="false">
<core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" slot="start" [showAlt]="false" <core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" slot="start" [showAlt]="false"
[modname]="event.modulename" [componentId]="event.instance"> [modname]="event.modulename" [componentId]="event.instance">

View File

@ -45,6 +45,8 @@ import {
CoreSwipeSlidesDynamicItem, CoreSwipeSlidesDynamicItem,
CoreSwipeSlidesDynamicItemsManagerSource, CoreSwipeSlidesDynamicItemsManagerSource,
} from '@classes/items-management/swipe-slides-dynamic-items-manager-source'; } 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. * 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. * Navigate to a particular event.
* *
* @param eventId Event to load. * @param eventId Event to load.
* @param day Day.
*/ */
gotoEvent(eventId: number): void { gotoEvent(eventId: number, day: PreloadedDay): void {
CoreNavigator.navigateToSitePath(`/calendar/event/${eventId}`); 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.manualSyncObserver?.off();
this.onlineObserver?.unsubscribe(); this.onlineObserver?.unsubscribe();
this.filterChangedObserver?.off(); this.filterChangedObserver?.off();
this.manager?.getSource().forgetRelatedSources();
this.manager?.destroy(); this.manager?.destroy();
this.managerUnsubscribe && this.managerUnsubscribe(); this.managerUnsubscribe && this.managerUnsubscribe();
} }
@ -483,6 +487,7 @@ type PreloadedDay = DayBasicData & CoreSwipeSlidesDynamicItem & {
class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicItemsManagerSource<PreloadedDay> { class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicItemsManagerSource<PreloadedDay> {
courses: Partial<CoreEnrolledCourseData>[] = []; courses: Partial<CoreEnrolledCourseData>[] = [];
eventsSources: Set<AddonCalendarEventsSource> = new Set();
// Offline events classified in month & day. // Offline events classified in month & day.
offlineEvents: Record<string, Record<number, AddonCalendarEventToDisplay[]>> = {}; offlineEvents: Record<string, Record<number, AddonCalendarEventToDisplay[]>> = {};
offlineEditedEventsIds: number[] = []; // IDs of events edited in offline. offlineEditedEventsIds: number[] = []; // IDs of events edited in offline.
@ -533,6 +538,8 @@ class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicIte
*/ */
filterEvents(day: PreloadedDay, filter: AddonCalendarFilter): void { filterEvents(day: PreloadedDay, filter: AddonCalendarFilter): void {
day.filteredEvents = AddonCalendarHelper.getFilteredEvents(day.events || [], filter, this.categories || {}); 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<void> {
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();
}
} }

View File

@ -26,7 +26,7 @@
</ion-buttons> </ion-buttons>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content [core-swipe-navigation]="events">
<ion-refresher slot="fixed" [disabled]="!eventLoaded" (ionRefresh)="doRefresh($event.target)"> <ion-refresher slot="fixed" [disabled]="!eventLoaded" (ionRefresh)="doRefresh($event.target)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher> </ion-refresher>

View File

@ -36,9 +36,12 @@ import { Network, NgZone, Translate } from '@singletons';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
import { CoreConstants } from '@/core/constants'; import { CoreConstants } from '@/core/constants';
import { AddonCalendarReminderTimeModalComponent } from '@addons/calendar/components/reminder-time-modal/reminder-time-modal'; 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. * Page that displays a single calendar event.
@ -63,6 +66,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
eventLoaded = false; eventLoaded = false;
event?: AddonCalendarEventToDisplay; event?: AddonCalendarEventToDisplay;
events?: CoreSwipeNavigationItemsManager;
courseId?: number; courseId?: number;
courseName = ''; courseName = '';
groupName?: string; groupName?: string;
@ -155,7 +159,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
/** /**
* View loaded. * View loaded.
*/ */
ngOnInit(): void { async ngOnInit(): Promise<void> {
try { try {
this.eventId = CoreNavigator.getRequiredRouteNumberParam('id'); this.eventId = CoreNavigator.getRequiredRouteNumberParam('id');
} catch (error) { } catch (error) {
@ -168,7 +172,8 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
this.syncIcon = CoreConstants.ICON_LOADING; 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; this.syncIcon = CoreConstants.ICON_SYNC;
} }
/**
* Initialize swipe manager if enabled.
*/
protected async initializeSwipeManager(): Promise<void> {
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. * Sync offline events.
* *
@ -620,7 +644,22 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
this.manualSyncObserver.off(); this.manualSyncObserver.off();
this.onlineObserver.unsubscribe(); this.onlineObserver.unsubscribe();
this.newEventObserver.off(); this.newEventObserver.off();
this.events?.destroy();
clearInterval(this.updateCurrentTime); 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;
}
}

View File

@ -30,6 +30,26 @@ export class CoreRoutedItemsManagerSourcesTracker {
private static instances: WeakMap<SourceConstructor, Instances> = new WeakMap(); private static instances: WeakMap<SourceConstructor, Instances> = new WeakMap();
private static instanceIds: WeakMap<CoreRoutedItemsManagerSource, string> = new WeakMap(); private static instanceIds: WeakMap<CoreRoutedItemsManagerSource, string> = 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<T extends CoreRoutedItemsManagerSource, C extends SourceConstructor<T>>(
constructor: C,
constructorArgumentsOrId: ConstructorParameters<C> | string,
): SourceConstuctorInstance<C> | null {
const id = typeof constructorArgumentsOrId === 'string'
? constructorArgumentsOrId
: constructor.getSourceId(...constructorArgumentsOrId);
const constructorInstances = this.getConstructorInstances(constructor);
return constructorInstances[id]?.instance as SourceConstuctorInstance<C>
?? null;
}
/** /**
* Create an instance of the given source or retrieve one if it's already in use. * 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<C>, constructorArguments: ConstructorParameters<C>,
): SourceConstuctorInstance<C> { ): SourceConstuctorInstance<C> {
const id = constructor.getSourceId(...constructorArguments); const id = constructor.getSourceId(...constructorArguments);
const constructorInstances = this.getConstructorInstances(constructor);
return constructorInstances[id]?.instance as SourceConstuctorInstance<C> // eslint-disable-next-line @typescript-eslint/no-explicit-any
return this.getSource(constructor, id) as any
?? this.createInstance(id, constructor, constructorArguments); ?? this.createInstance(id, constructor, constructorArguments);
} }