Merge pull request #3302 from NoelDeMartin/MOBILE-4042
MOBILE-4042 calendar: Swipe navigation in eventmain
commit
11c1b2a7da
|
@ -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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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">
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue