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">
|
||||
<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">
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue