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">
<ion-card>
<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">
<core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" slot="start" [showAlt]="false"
[modname]="event.modulename" [componentId]="event.instance">

View File

@ -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<PreloadedDay> {
courses: Partial<CoreEnrolledCourseData>[] = [];
eventsSources: Set<AddonCalendarEventsSource> = new Set();
// Offline events classified in month & day.
offlineEvents: Record<string, Record<number, AddonCalendarEventToDisplay[]>> = {};
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<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-toolbar>
</ion-header>
<ion-content>
<ion-content [core-swipe-navigation]="events">
<ion-refresher slot="fixed" [disabled]="!eventLoaded" (ionRefresh)="doRefresh($event.target)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>

View File

@ -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<void> {
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<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.
*
@ -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;
}
}

View File

@ -30,6 +30,26 @@ export class CoreRoutedItemsManagerSourcesTracker {
private static instances: WeakMap<SourceConstructor, Instances> = 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.
*
@ -42,9 +62,9 @@ export class CoreRoutedItemsManagerSourcesTracker {
constructorArguments: ConstructorParameters<C>,
): SourceConstuctorInstance<C> {
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);
}