+ {{ event.timestart * 1000 | coreFormatDate: timeFormat }}
{{event.name}}
diff --git a/src/addon/calendar/components/calendar/calendar.ts b/src/addon/calendar/components/calendar/calendar.ts
index 053863483..203db79e8 100644
--- a/src/addon/calendar/components/calendar/calendar.ts
+++ b/src/addon/calendar/components/calendar/calendar.ts
@@ -42,6 +42,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
weekDays: any[];
weeks: any[];
loaded = false;
+ timeFormat: string;
protected year: number;
protected month: number;
@@ -141,6 +142,11 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
this.deletedEvents = ids;
}));
+ // Get time format to use.
+ promises.push(this.calendarProvider.getCalendarTimeFormat().then((value) => {
+ this.timeFormat = value;
+ }));
+
return Promise.all(promises).then(() => {
return this.fetchEvents();
}).catch((error) => {
@@ -232,6 +238,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
promises.push(this.calendarProvider.invalidateMonthlyEvents(this.year, this.month));
promises.push(this.coursesProvider.invalidateCategories(0, true));
+ promises.push(this.calendarProvider.invalidateTimeFormat());
this.categoriesRetrieved = false; // Get categories again.
diff --git a/src/addon/calendar/components/components.module.ts b/src/addon/calendar/components/components.module.ts
index a6d5afe22..3928f6dd9 100644
--- a/src/addon/calendar/components/components.module.ts
+++ b/src/addon/calendar/components/components.module.ts
@@ -18,23 +18,28 @@ import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
+import { CorePipesModule } from '@pipes/pipes.module';
import { AddonCalendarCalendarComponent } from '../components/calendar/calendar';
+import { AddonCalendarUpcomingEventsComponent } from '../components/upcoming-events/upcoming-events';
@NgModule({
declarations: [
- AddonCalendarCalendarComponent
+ AddonCalendarCalendarComponent,
+ AddonCalendarUpcomingEventsComponent
],
imports: [
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
- CoreDirectivesModule
+ CoreDirectivesModule,
+ CorePipesModule
],
providers: [
],
exports: [
- AddonCalendarCalendarComponent
+ AddonCalendarCalendarComponent,
+ AddonCalendarUpcomingEventsComponent
]
})
export class AddonCalendarComponentsModule {}
diff --git a/src/addon/calendar/components/upcoming-events/addon-calendar-upcoming-events.html b/src/addon/calendar/components/upcoming-events/addon-calendar-upcoming-events.html
new file mode 100644
index 000000000..b338f0eca
--- /dev/null
+++ b/src/addon/calendar/components/upcoming-events/addon-calendar-upcoming-events.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'core.notsent' | translate }}
+
+
+
+ {{ 'core.deletedoffline' | translate }}
+
+
+
+
+
+
diff --git a/src/addon/calendar/components/upcoming-events/upcoming-events.ts b/src/addon/calendar/components/upcoming-events/upcoming-events.ts
new file mode 100644
index 000000000..41751c0d5
--- /dev/null
+++ b/src/addon/calendar/components/upcoming-events/upcoming-events.ts
@@ -0,0 +1,317 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// 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 { Component, OnDestroy, OnInit, Input, OnChanges, SimpleChange, Output, EventEmitter } from '@angular/core';
+import { CoreEventsProvider } from '@providers/events';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { AddonCalendarProvider } from '../../providers/calendar';
+import { AddonCalendarHelperProvider } from '../../providers/helper';
+import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
+import { CoreCoursesProvider } from '@core/courses/providers/courses';
+import { CoreConstants } from '@core/constants';
+
+/**
+ * Component that displays upcoming events.
+ */
+@Component({
+ selector: 'addon-calendar-upcoming-events',
+ templateUrl: 'addon-calendar-upcoming-events.html',
+})
+export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, OnDestroy {
+ @Input() courseId: number | string;
+ @Input() categoryId: number | string; // Category ID the course belongs to.
+ @Output() onEventClicked = new EventEmitter
();
+
+ events = []; // Events (both online and offline).
+ filteredEvents = [];
+ loaded = false;
+
+ protected year: number;
+ protected month: number;
+ protected categoriesRetrieved = false;
+ protected categories = {};
+ protected currentSiteId: string;
+ protected onlineEvents = [];
+ protected offlineEvents = []; // Offline events.
+ protected deletedEvents = []; // Events deleted in offline.
+ protected lookAhead: number;
+ protected timeFormat: string;
+
+ // Observers.
+ protected undeleteEventObserver: any;
+
+ constructor(eventsProvider: CoreEventsProvider,
+ sitesProvider: CoreSitesProvider,
+ private calendarProvider: AddonCalendarProvider,
+ private calendarHelper: AddonCalendarHelperProvider,
+ private calendarOffline: AddonCalendarOfflineProvider,
+ private domUtils: CoreDomUtilsProvider,
+ private coursesProvider: CoreCoursesProvider) {
+
+ this.currentSiteId = sitesProvider.getCurrentSiteId();
+
+ // Listen for events "undeleted" (offline).
+ this.undeleteEventObserver = eventsProvider.on(AddonCalendarProvider.UNDELETED_EVENT_EVENT, (data) => {
+ if (data && data.eventId) {
+ // Mark it as undeleted, no need to refresh.
+ this.undeleteEvent(data.eventId);
+
+ // Remove it from the list of deleted events if it's there.
+ const index = this.deletedEvents.indexOf(data.eventId);
+ if (index != -1) {
+ this.deletedEvents.splice(index, 1);
+ }
+ }
+ }, this.currentSiteId);
+ }
+
+ /**
+ * Component loaded.
+ */
+ ngOnInit(): void {
+ this.fetchData();
+ }
+
+ /**
+ * Detect changes on input properties.
+ */
+ ngOnChanges(changes: {[name: string]: SimpleChange}): void {
+ if (changes.courseId || changes.categoryId) {
+ this.filterEvents();
+ }
+ }
+
+ /**
+ * Fetch data.
+ *
+ * @param {boolean} [refresh=false] True if we are refreshing events.
+ * @return {Promise} Promise resolved when done.
+ */
+ fetchData(refresh: boolean = false): Promise {
+ const promises = [];
+
+ promises.push(this.loadCategories());
+
+ // Get offline events.
+ promises.push(this.calendarOffline.getAllEditedEvents().then((events) => {
+ // Format data.
+ events.forEach((event) => {
+ event.offline = true;
+ this.calendarHelper.formatEventData(event);
+ });
+
+ this.offlineEvents = this.sortEvents(events);
+ }));
+
+ // Get events deleted in offline.
+ promises.push(this.calendarOffline.getAllDeletedEventsIds().then((ids) => {
+ this.deletedEvents = ids;
+ }));
+
+ // Get user preferences.
+ promises.push(this.calendarProvider.getCalendarLookAhead().then((value) => {
+ this.lookAhead = value;
+ }));
+
+ promises.push(this.calendarProvider.getCalendarTimeFormat().then((value) => {
+ this.timeFormat = value;
+ }));
+
+ return Promise.all(promises).then(() => {
+ return this.fetchEvents();
+ }).catch((error) => {
+ this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
+ }).finally(() => {
+ this.loaded = true;
+ });
+ }
+
+ /**
+ * Fetch upcoming events.
+ *
+ * @return {Promise} Promise resolved when done.
+ */
+ fetchEvents(): Promise {
+ // Don't pass courseId and categoryId, we'll filter them locally.
+ return this.calendarProvider.getUpcomingEvents().then((result) => {
+ this.onlineEvents = result.events;
+
+ this.onlineEvents.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
+
+ // Merge the online events with offline data.
+ this.events = this.mergeEvents();
+
+ // Filter events by course.
+ this.filterEvents();
+
+ // Re-calculate the formatted time so it uses the device date.
+ this.events.forEach((event) => {
+ event.formattedtime = this.calendarProvider.formatEventTime(event, this.timeFormat);
+ });
+ });
+ }
+
+ /**
+ * Load categories to be able to filter events.
+ *
+ * @return {Promise} Promise resolved when done.
+ */
+ protected loadCategories(): Promise {
+ if (this.categoriesRetrieved) {
+ // Already retrieved, stop.
+ return Promise.resolve();
+ }
+
+ return this.coursesProvider.getCategories(0, true).then((cats) => {
+ this.categoriesRetrieved = true;
+ this.categories = {};
+
+ // Index categories by ID.
+ cats.forEach((category) => {
+ this.categories[category.id] = category;
+ });
+ }).catch(() => {
+ // Ignore errors.
+ });
+ }
+
+ /**
+ * Filter events to only display events belonging to a certain course.
+ */
+ filterEvents(): void {
+ const courseId = this.courseId ? Number(this.courseId) : undefined,
+ categoryId = this.categoryId ? Number(this.categoryId) : undefined;
+
+ if (!courseId || courseId < 0) {
+ this.filteredEvents = this.events;
+ } else {
+ this.filteredEvents = this.events.filter((event) => {
+ return this.calendarHelper.shouldDisplayEvent(event, courseId, categoryId, this.categories);
+ });
+ }
+ }
+
+ /**
+ * Refresh events.
+ *
+ * @param {boolean} [sync] Whether it should try to synchronize offline events.
+ * @param {boolean} [showErrors] Whether to show sync errors to the user.
+ * @return {Promise} Promise resolved when done.
+ */
+ refreshData(sync?: boolean, showErrors?: boolean): Promise {
+ const promises = [];
+
+ promises.push(this.calendarProvider.invalidateAllUpcomingEvents());
+ promises.push(this.coursesProvider.invalidateCategories(0, true));
+ promises.push(this.calendarProvider.invalidateLookAhead());
+ promises.push(this.calendarProvider.invalidateTimeFormat());
+
+ this.categoriesRetrieved = false; // Get categories again.
+
+ return Promise.all(promises).then(() => {
+ return this.fetchData(true);
+ });
+ }
+
+ /**
+ * An event was clicked.
+ *
+ * @param {any} event Event.
+ */
+ eventClicked(event: any): void {
+ this.onEventClicked.emit(event.id);
+ }
+
+ /**
+ * Merge online events with the offline events of that period.
+ *
+ * @return {any[]} Merged events.
+ */
+ protected mergeEvents(): any[] {
+ if (!this.offlineEvents.length && !this.deletedEvents.length) {
+ // No offline events, nothing to merge.
+ return this.onlineEvents;
+ }
+
+ const start = Date.now(),
+ end = start + (CoreConstants.SECONDS_DAY * this.lookAhead);
+ let result = this.onlineEvents;
+
+ if (this.deletedEvents.length) {
+ // Mark as deleted the events that were deleted in offline.
+ result.forEach((event) => {
+ event.deleted = this.deletedEvents.indexOf(event.id) != -1;
+ });
+ }
+
+ if (this.offlineEvents.length) {
+ // Remove the online events that were modified in offline.
+ result = result.filter((event) => {
+ const offlineEvent = this.offlineEvents.find((ev) => {
+ return ev.id == event.id;
+ });
+
+ return !offlineEvent;
+ });
+ }
+
+ // Now get the offline events that belong to this period.
+ const periodOfflineEvents = this.offlineEvents.filter((event) => {
+ return (event.timestart >= start || event.timestart + event.timeduration >= start) && event.timestart <= end;
+ });
+
+ // Merge both arrays and sort them.
+ result = result.concat(periodOfflineEvents);
+
+ return this.sortEvents(result);
+ }
+
+ /**
+ * Sort events by timestart.
+ *
+ * @param {any[]} events List to sort.
+ */
+ protected sortEvents(events: any[]): any[] {
+ return events.sort((a, b) => {
+ if (a.timestart == b.timestart) {
+ return a.timeduration - b.timeduration;
+ }
+
+ return a.timestart - b.timestart;
+ });
+ }
+
+ /**
+ * Undelete a certain event.
+ *
+ * @param {number} eventId Event ID.
+ */
+ protected undeleteEvent(eventId: number): void {
+ const event = this.onlineEvents.find((event) => {
+ return event.id == eventId;
+ });
+
+ if (event) {
+ event.deleted = false;
+ }
+ }
+
+ /**
+ * Component destroyed.
+ */
+ ngOnDestroy(): void {
+ this.undeleteEventObserver && this.undeleteEventObserver.off();
+ }
+}
diff --git a/src/addon/calendar/lang/en.json b/src/addon/calendar/lang/en.json
index 412c81742..ba81a9a89 100644
--- a/src/addon/calendar/lang/en.json
+++ b/src/addon/calendar/lang/en.json
@@ -1,4 +1,5 @@
{
+ "allday": "All day",
"calendar": "Calendar",
"calendarevent": "Calendar event",
"calendarevents": "Calendar events",
@@ -29,6 +30,7 @@
"invalidtimedurationuntil": "The date and time you selected for duration until is before the start time of the event. Please correct this before proceeding.",
"mon": "Mon",
"monday": "Monday",
+ "monthlyview": "Monthly view",
"newevent": "New event",
"noevents": "There are no events",
"nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event",
@@ -45,6 +47,8 @@
"sunday": "Sunday",
"thu": "Thu",
"thursday": "Thursday",
+ "today": "Today",
+ "tomorrow": "Tomorrow",
"tue": "Tue",
"tuesday": "Tuesday",
"typeclose": "Close event",
@@ -56,6 +60,8 @@
"typeopen": "Open event",
"typesite": "Site event",
"typeuser": "User event",
+ "upcomingevents": "Upcoming events",
"wed": "Wed",
- "wednesday": "Wednesday"
+ "wednesday": "Wednesday",
+ "yesterday": "Yesterday"
}
\ No newline at end of file
diff --git a/src/addon/calendar/pages/index/index.html b/src/addon/calendar/pages/index/index.html
index fa0877b46..120c9dc2c 100644
--- a/src/addon/calendar/pages/index/index.html
+++ b/src/addon/calendar/pages/index/index.html
@@ -2,6 +2,12 @@
{{ 'addon.calendar.calendarevents' | translate }}
+
+
@@ -22,7 +28,9 @@
{{ 'core.hasdatatosync' | translate:{$a: 'addon.calendar.calendar' | translate} }}
-
+
+
+
diff --git a/src/addon/calendar/pages/index/index.ts b/src/addon/calendar/pages/index/index.ts
index 8f134ed90..396690427 100644
--- a/src/addon/calendar/pages/index/index.ts
+++ b/src/addon/calendar/pages/index/index.ts
@@ -23,6 +23,7 @@ import { AddonCalendarProvider } from '../../providers/calendar';
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarCalendarComponent } from '../../components/calendar/calendar';
+import { AddonCalendarUpcomingEventsComponent } from '../../components/upcoming-events/upcoming-events';
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
@@ -39,6 +40,7 @@ import { Network } from '@ionic-native/network';
})
export class AddonCalendarIndexPage implements OnInit, OnDestroy {
@ViewChild(AddonCalendarCalendarComponent) calendarComponent: AddonCalendarCalendarComponent;
+ @ViewChild(AddonCalendarUpcomingEventsComponent) upcomingEventsComponent: AddonCalendarUpcomingEventsComponent;
protected allCourses = {
id: -1,
@@ -67,6 +69,8 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
hasOffline = false;
isOnline = false;
syncIcon: string;
+ showCalendar = true;
+ loadUpcoming = false;
constructor(localNotificationsProvider: CoreLocalNotificationsProvider,
navParams: NavParams,
@@ -274,7 +278,11 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
}));
// Refresh the sub-component.
- promises.push(this.calendarComponent.refreshData());
+ if (this.showCalendar && this.calendarComponent) {
+ promises.push(this.calendarComponent.refreshData());
+ } else if (!this.showCalendar && this.upcomingEventsComponent) {
+ promises.push(this.upcomingEventsComponent.refreshData());
+ }
return Promise.all(promises).finally(() => {
return this.fetchData(sync, showErrors);
@@ -349,6 +357,17 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
this.navCtrl.push('AddonCalendarSettingsPage');
}
+ /**
+ * Toogle display: monthly view or upcoming events.
+ */
+ toggleDisplay(): void {
+ this.showCalendar = !this.showCalendar;
+
+ if (!this.showCalendar) {
+ this.loadUpcoming = true;
+ }
+ }
+
/**
* Page destroyed.
*/
diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts
index 4705a8ad8..c759210db 100644
--- a/src/addon/calendar/providers/calendar.ts
+++ b/src/addon/calendar/providers/calendar.ts
@@ -27,7 +27,9 @@ import { CoreConfigProvider } from '@providers/config';
import { ILocalNotification } from '@ionic-native/local-notifications';
import { SQLiteDB } from '@classes/sqlitedb';
import { AddonCalendarOfflineProvider } from './calendar-offline';
+import { CoreUserProvider } from '@core/user/providers/user';
import { TranslateService } from '@ngx-translate/core';
+import * as moment from 'moment';
/**
* Service to handle calendar events.
@@ -49,6 +51,10 @@ export class AddonCalendarProvider {
static TYPE_GROUP = 'group';
static TYPE_SITE = 'site';
static TYPE_USER = 'user';
+
+ static CALENDAR_TF_24 = '%H:%M'; // Calendar time in 24 hours format.
+ static CALENDAR_TF_12 = '%I:%M %p'; // Calendar time in 12 hours format.
+
protected ROOT_CACHE_KEY = 'mmaCalendar:';
protected weekDays = [
@@ -249,11 +255,19 @@ export class AddonCalendarProvider {
protected logger;
- constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private groupsProvider: CoreGroupsProvider,
- private coursesProvider: CoreCoursesProvider, private timeUtils: CoreTimeUtilsProvider,
- private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider,
- private utils: CoreUtilsProvider, private calendarOffline: AddonCalendarOfflineProvider,
- private appProvider: CoreAppProvider, private translate: TranslateService) {
+ constructor(logger: CoreLoggerProvider,
+ private sitesProvider: CoreSitesProvider,
+ private groupsProvider: CoreGroupsProvider,
+ private coursesProvider: CoreCoursesProvider,
+ private timeUtils: CoreTimeUtilsProvider,
+ private localNotificationsProvider: CoreLocalNotificationsProvider,
+ private configProvider: CoreConfigProvider,
+ private utils: CoreUtilsProvider,
+ private calendarOffline: AddonCalendarOfflineProvider,
+ private appProvider: CoreAppProvider,
+ private translate: TranslateService,
+ private userProvider: CoreUserProvider) {
+
this.logger = logger.getInstance('AddonCalendarProvider');
this.sitesProvider.registerSiteSchema(this.siteSchema);
}
@@ -456,6 +470,90 @@ export class AddonCalendarProvider {
});
}
+ /**
+ * Check if event ends the same day or not.
+ *
+ * @param {any} event Event info.
+ * @return {boolean} If the .
+ */
+ endsSameDay(event: any): boolean {
+ if (!event.timeduration) {
+ // No duration.
+ return true;
+ }
+
+ // Check if day has changed.
+ return moment(event.timestart * 1000).isSame((event.timestart + event.timeduration) * 1000, 'day');
+ }
+
+ /**
+ * Format event time. Equivalent to calendar_format_event_time.
+ *
+ * @param {any} event Event to format.
+ * @param {string} format Calendar time format (from getCalendarTimeFormat).
+ * @param {boolean} [useCommonWords=true] Whether to use common words like "Today", "Yesterday", etc.
+ * @param {number} [showTime=0] Determine the show time GMT timestamp.
+ * @return {string} Formatted event time.
+ */
+ formatEventTime(event: any, format: string, useCommonWords: boolean = true, showTime: number = 0): string {
+ const start = event.timestart * 1000,
+ end = (event.timestart + event.timeduration) * 1000;
+ let time;
+
+ if (event.timeduration) {
+
+ if (moment(start).isSame(end, 'day')) {
+ // Event starts and ends the same day.
+ if (event.timeduration == CoreConstants.SECONDS_DAY) {
+ time = this.translate.instant('addon.calendar.allday');
+ } else {
+ time = this.timeUtils.userDate(start, format) + ' » ' +
+ this.timeUtils.userDate(end, format);
+ }
+
+ if (!showTime) {
+ return this.getDayRepresentation(start, useCommonWords) + ', ' + time;
+ } else {
+ return time;
+ }
+
+ } else {
+ // Event lasts more than one day.
+ const midnightStart = moment(start).startOf('day').unix() * 1000,
+ midnightEnd = moment(end).startOf('day').unix() * 1000,
+ timeStart = this.timeUtils.userDate(start, format),
+ timeEnd = this.timeUtils.userDate(end, format);
+ let dayStart = this.getDayRepresentation(start, useCommonWords) + ', ',
+ dayEnd = this.getDayRepresentation(end, useCommonWords) + ', ';
+
+ if (showTime == midnightStart) {
+ dayStart = '';
+ }
+
+ if (showTime == midnightEnd) {
+ dayEnd = '';
+ }
+
+ // Set printable representation.
+ if (moment().isSame(start, 'day')) {
+ // Event starts today, don't display the day.
+ return timeStart + ' » ' + dayEnd + timeEnd;
+ } else {
+ // The event starts in the future, print both days.
+ return dayStart + timeStart + ' » ' + dayEnd + timeEnd;
+ }
+ }
+ } else { // There is no time duration.
+ const time = this.timeUtils.userDate(start, format);
+
+ if (!showTime) {
+ return this.getDayRepresentation(start, useCommonWords) + ', ' + time.trim();
+ } else {
+ return time;
+ }
+ }
+ }
+
/**
* Get access information for a calendar (either course calendar or site calendar).
*
@@ -545,6 +643,84 @@ export class AddonCalendarProvider {
return this.ROOT_CACHE_KEY + 'allowedEventTypes:' + (courseId || 0);
}
+ /**
+ * Get the "look ahead" for a certain user.
+ *
+ * @param {string} [siteId] ID of the site. If not defined, use current site.
+ * @return {Promise} Promise resolved with the look ahead (number of days).
+ */
+ getCalendarLookAhead(siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ return this.userProvider.getUserPreference('calendar_lookahead').catch((error) => {
+ // Ignore errors.
+ }).then((value): any => {
+ if (typeof value != 'undefined') {
+ return value;
+ }
+
+ return site.getStoredConfig('calendar_lookahead');
+ });
+ });
+ }
+
+ /**
+ * Get the time format to use in calendar.
+ *
+ * @param {string} [siteId] ID of the site. If not defined, use current site.
+ * @return {Promise} Promise resolved with the format.
+ */
+ getCalendarTimeFormat(siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ return this.userProvider.getUserPreference('calendar_timeformat').catch((error) => {
+ // Ignore errors.
+ }).then((format) => {
+
+ if (!format || format === '0') {
+ format = site.getStoredConfig('calendar_site_timeformat');
+ }
+
+ if (format === AddonCalendarProvider.CALENDAR_TF_12) {
+ format = this.translate.instant('core.strftimetime12');
+ } else if (format === AddonCalendarProvider.CALENDAR_TF_24) {
+ format = this.translate.instant('core.strftimetime24');
+ }
+
+ return format && format !== '0' ? format : this.translate.instant('core.strftimetime');
+ });
+ });
+ }
+
+ /**
+ * Return the representation day. Equivalent to Moodle's calendar_day_representation.
+ *
+ * @param {number} time Timestamp to get the day from.
+ * @param {boolean} [useCommonWords=true] Whether to use common words like "Today", "Yesterday", etc.
+ * @return {string} The formatted date/time.
+ */
+ getDayRepresentation(time: number, useCommonWords: boolean = true): string {
+
+ if (!useCommonWords) {
+ // We don't want words, just a date.
+ return this.timeUtils.userDate(time, 'core.strftimedayshort');
+ }
+
+ const date = moment(time),
+ today = moment();
+
+ if (date.isSame(today, 'day')) {
+ return this.translate.instant('addon.calendar.today');
+
+ } else if (date.isSame(today.clone().subtract(1, 'days'), 'day')) {
+ return this.translate.instant('addon.calendar.yesterday');
+
+ } else if (date.isSame(today.clone().add(1, 'days'), 'day')) {
+ return this.translate.instant('addon.calendar.tomorrow');
+
+ } else {
+ return this.timeUtils.userDate(time, 'core.strftimedayshort');
+ }
+ }
+
/**
* Get the configured default notification time.
*
@@ -1019,6 +1195,7 @@ export class AddonCalendarProvider {
*
* @param {number} [courseId] Course ID.
* @param {number} [categoryId] Category ID.
+ * @param {string} [siteId] Site Id. If not defined, use current site.
* @return {Promise} Promise resolved when the data is invalidated.
*/
invalidateUpcomingEvents(courseId?: number, categoryId?: number, siteId?: string): Promise {
@@ -1027,6 +1204,26 @@ export class AddonCalendarProvider {
});
}
+ /**
+ * Invalidates look ahead setting.
+ *
+ * @param {string} [siteId] Site Id. If not defined, use current site.
+ * @return {Promise} Promise resolved when the data is invalidated.
+ */
+ invalidateLookAhead(siteId?: string): Promise {
+ return this.userProvider.invalidateUserPreference('calendar_lookahead', siteId);
+ }
+
+ /**
+ * Invalidates time format setting.
+ *
+ * @param {string} [siteId] Site Id. If not defined, use current site.
+ * @return {Promise} Promise resolved when the data is invalidated.
+ */
+ invalidateTimeFormat(siteId?: string): Promise {
+ return this.userProvider.invalidateUserPreference('calendar_timeformat', siteId);
+ }
+
/**
* Check if Calendar is disabled in a certain site.
*
diff --git a/src/app/app.scss b/src/app/app.scss
index 41759e8ef..7123b9909 100644
--- a/src/app/app.scss
+++ b/src/app/app.scss
@@ -1152,3 +1152,8 @@ ion-app.platform-desktop {
min-height: $button-ios-small-height;
}
}
+
+// Make funnel icon have iOS look.
+.ion-md-funnel::before {
+ content: "\f182";
+}
diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json
index 3e4a0a984..7dae77a70 100644
--- a/src/assets/lang/en.json
+++ b/src/assets/lang/en.json
@@ -83,6 +83,7 @@
"addon.blog.publishtoworld": "Anyone in the world",
"addon.blog.showonlyyourentries": "Show only your entries",
"addon.blog.siteblogheading": "Site blog",
+ "addon.calendar.allday": "All day",
"addon.calendar.calendar": "Calendar",
"addon.calendar.calendarevent": "Calendar event",
"addon.calendar.calendarevents": "Calendar events",
@@ -113,6 +114,7 @@
"addon.calendar.invalidtimedurationuntil": "The date and time you selected for duration until is before the start time of the event. Please correct this before proceeding.",
"addon.calendar.mon": "Mon",
"addon.calendar.monday": "Monday",
+ "addon.calendar.monthlyview": "Monthly view",
"addon.calendar.newevent": "New event",
"addon.calendar.noevents": "There are no events",
"addon.calendar.nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event",
@@ -129,6 +131,8 @@
"addon.calendar.sunday": "Sunday",
"addon.calendar.thu": "Thu",
"addon.calendar.thursday": "Thursday",
+ "addon.calendar.today": "Today",
+ "addon.calendar.tomorrow": "Tomorrow",
"addon.calendar.tue": "Tue",
"addon.calendar.tuesday": "Tuesday",
"addon.calendar.typecategory": "Category event",
@@ -140,8 +144,10 @@
"addon.calendar.typeopen": "Open event",
"addon.calendar.typesite": "Site event",
"addon.calendar.typeuser": "User event",
+ "addon.calendar.upcomingevents": "Upcoming events",
"addon.calendar.wed": "Wed",
"addon.calendar.wednesday": "Wednesday",
+ "addon.calendar.yesterday": "Yesterday",
"addon.competency.activities": "Activities",
"addon.competency.competencies": "Competencies",
"addon.competency.competenciesmostoftennotproficientincourse": "Competencies most often not proficient in this course",