From 5d910ea5b49d234c74fcc3747421afc1555d0467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 8 Nov 2022 17:31:02 +0100 Subject: [PATCH] MOBILE-3936 reminders: Add reminders to courses and activities --- .../course/components/components.module.ts | 2 + .../module-info/core-course-module-info.html | 7 +- .../module-info/course-module-info.scss | 8 -- .../components/module/core-course-module.html | 7 +- .../pages/course-summary/course-summary.html | 18 ++- .../course-summary/course-summary.module.ts | 3 + .../features/course/services/course-helper.ts | 13 +- src/core/features/course/services/course.ts | 46 +------ .../reminders/components/components.module.ts | 10 +- .../reminders/components/date/date.html | 8 ++ .../reminders/components/date/date.scss | 21 ++++ .../reminders/components/date/date.ts | 95 ++++++++++++++ .../components/set-button/set-button.html | 4 + .../components/set-button/set-button.ts | 116 ++++++++++++++++++ 14 files changed, 278 insertions(+), 80 deletions(-) create mode 100644 src/core/features/reminders/components/date/date.html create mode 100644 src/core/features/reminders/components/date/date.scss create mode 100644 src/core/features/reminders/components/date/date.ts create mode 100644 src/core/features/reminders/components/set-button/set-button.html create mode 100644 src/core/features/reminders/components/set-button/set-button.ts diff --git a/src/core/features/course/components/components.module.ts b/src/core/features/course/components/components.module.ts index a8bbc04f1..f663770b5 100644 --- a/src/core/features/course/components/components.module.ts +++ b/src/core/features/course/components/components.module.ts @@ -29,6 +29,7 @@ import { CoreCourseModuleManualCompletionComponent } from './module-manual-compl import { CoreCourseModuleNavigationComponent } from './module-navigation/module-navigation'; import { CoreCourseModuleSummaryComponent } from './module-summary/module-summary'; import { CoreCourseCourseIndexTourComponent } from './course-index-tour/course-index-tour'; +import { CoreRemindersComponentsModule } from '@features/reminders/components/components.module'; @NgModule({ declarations: [ @@ -48,6 +49,7 @@ import { CoreCourseCourseIndexTourComponent } from './course-index-tour/course-i ], imports: [ CoreBlockComponentsModule, + CoreRemindersComponentsModule, CoreSharedModule, ], exports: [ diff --git a/src/core/features/course/components/module-info/core-course-module-info.html b/src/core/features/course/components/module-info/core-course-module-info.html index b338fc7d1..1e00b23b0 100644 --- a/src/core/features/course/components/module-info/core-course-module-info.html +++ b/src/core/features/course/components/module-info/core-course-module-info.html @@ -26,10 +26,9 @@
-

- {{ date.label }} - {{ date.readableTime }} -

+ +
diff --git a/src/core/features/course/components/module-info/course-module-info.scss b/src/core/features/course/components/module-info/course-module-info.scss index bf6e0d5ce..533696aae 100644 --- a/src/core/features/course/components/module-info/course-module-info.scss +++ b/src/core/features/course/components/module-info/course-module-info.scss @@ -48,12 +48,6 @@ padding-top: 8px; } - .core-module-dates ion-icon { - margin-left: 4px; - margin-right: 4px; - } - - .core-module-dates, .core-module-availabilityinfo { font-size: 90%; ion-icon { @@ -94,8 +88,6 @@ white-space: normal !important; } } - - } :host-context(.core-iframe-fullscreen) { diff --git a/src/core/features/course/components/module/core-course-module.html b/src/core/features/course/components/module/core-course-module.html index 8a099383d..5206b6397 100644 --- a/src/core/features/course/components/module/core-course-module.html +++ b/src/core/features/course/components/module/core-course-module.html @@ -81,10 +81,9 @@ *ngIf="(showActivityDates && module.dates && module.dates.length) || module.availabilityinfo">
-

- {{ date.label }} - {{ date.readableTime }} -

+ +
diff --git a/src/core/features/course/pages/course-summary/course-summary.html b/src/core/features/course/pages/course-summary/course-summary.html index 0336344b6..56e89e0fa 100644 --- a/src/core/features/course/pages/course-summary/course-summary.html +++ b/src/core/features/course/pages/course-summary/course-summary.html @@ -57,16 +57,14 @@
-

- - {{ 'core.course.startdate' | translate }}
- {{ course.startdate * 1000 | coreFormatDate:'strftimedaydatetime' }} -

-

- - {{ 'core.course.enddate' | translate }}
- {{ course.enddate * 1000 | coreFormatDate:'strftimedaydatetime' }} -

+ + + +
diff --git a/src/core/features/course/pages/course-summary/course-summary.module.ts b/src/core/features/course/pages/course-summary/course-summary.module.ts index daeb7f70c..c925524f7 100644 --- a/src/core/features/course/pages/course-summary/course-summary.module.ts +++ b/src/core/features/course/pages/course-summary/course-summary.module.ts @@ -17,6 +17,7 @@ import { RouterModule, Routes } from '@angular/router'; import { CoreSharedModule } from '@/core/shared.module'; import { CoreCourseSummaryPage } from './course-summary'; +import { CoreRemindersComponentsModule } from '@features/reminders/components/components.module'; const routes: Routes = [ { @@ -27,6 +28,7 @@ const routes: Routes = [ @NgModule({ imports: [ CoreSharedModule, + CoreRemindersComponentsModule, ], declarations: [ CoreCourseSummaryPage, @@ -39,6 +41,7 @@ export class CoreCoursePreviewPageComponentModule { } RouterModule.forChild(routes), CoreSharedModule, CoreCoursePreviewPageComponentModule, + CoreRemindersComponentsModule, ], exports: [RouterModule], }) diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index 58ae2f26d..65d242603 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -27,7 +27,6 @@ import { CoreCourseModuleCompletionTracking, CoreCourseModuleCompletionStatus, CoreCourseGetContentsWSModule, - CoreCourseGetContentsWSModuleDate, } from './course'; import { CoreConstants } from '@/core/constants'; import { CoreLogger } from '@singletons/logger'; @@ -1189,7 +1188,7 @@ export class CoreCourseHelperProvider { * This should be used in 3.6 sites or higher, where the course contents already include the completion. * * @param courseId The course to get the completion. - * @param mmodule The module. + * @param module The module. * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ @@ -2086,20 +2085,12 @@ export type CoreCourseSectionWithStatus = CoreCourseSection & { /** * Module with calculated data. */ -export type CoreCourseModuleData = Omit & { +export type CoreCourseModuleData = Omit & { course: number; // The course id. isStealth?: boolean; handlerData?: CoreCourseModuleHandlerData; completiondata?: CoreCourseModuleCompletionData; section: number; - dates?: CoreCourseModuleDate[]; -}; - -/** - * Module date with calculated data. - */ -export type CoreCourseModuleDate = CoreCourseGetContentsWSModuleDate & { - readableTime: string; }; /** diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index 16a680440..70b2ca22b 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -38,7 +38,7 @@ import { import { CoreDomUtils } from '@services/utils/dom'; import { CoreWSError } from '@classes/errors/wserror'; import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; -import { CoreCourseHelper, CoreCourseModuleData, CoreCourseModuleCompletionData, CoreCourseModuleDate } from './course-helper'; +import { CoreCourseHelper, CoreCourseModuleData, CoreCourseModuleCompletionData } from './course-helper'; import { CoreCourseFormatDelegate } from './format-delegate'; import { CoreCronDelegate } from '@services/cron'; import { CoreCourseLogCronHandler } from './handlers/log-cron'; @@ -53,7 +53,6 @@ import { CoreDatabaseTable } from '@classes/database/database-table'; import { CoreDatabaseCachingStrategy } from '@classes/database/database-table-proxy'; import { SQLiteDB } from '@classes/sqlitedb'; import { CorePlatform } from '@services/platform'; -import { CoreTime } from '@singletons/time'; import { asyncObservable, firstValueFrom } from '@/core/utils/rxjs'; import { map } from 'rxjs/operators'; @@ -678,39 +677,11 @@ export class CoreCourseProvider { }; } - let formattedDates: CoreCourseModuleDate[] | undefined; - - if (module.dates) { - formattedDates = module.dates.map(date => { - let readableTime = ''; - if (!date.relativeto) { - readableTime = CoreTimeUtils.userDate(date.timestamp * 1000, 'core.strftimedatetime', true); - } else { - readableTime = Translate.instant( - 'core.course.relativedatessubmissionduedate' + (date.timestamp > date.relativeto ? 'after' : 'before'), - { - $a: { - datediffstr: date.relativeto === date.timestamp ? - '0 ' + Translate.instant('core.secs') : - CoreTime.formatTime(date.relativeto - date.timestamp, 3), - }, - }, - ); - } - - return { - ...date, - readableTime, - }; - }); - } - return { ...module, course: courseId, section: sectionId, completiondata: completionData, - dates: formattedDates, }; } @@ -1775,7 +1746,10 @@ export type CoreCourseGetContentsWSModule = { completiondata?: CoreCourseModuleWSCompletionData; // Module completion data. contents?: CoreCourseModuleContentFile[]; downloadcontent?: number; // @since 4.0 The download content value. - dates?: CoreCourseGetContentsWSModuleDate[]; // @since 3.11. Activity dates. + dates?: { + label: string; + timestamp: number; + }[]; // @since 3.11. Activity dates. contentsinfo?: { // @since v3.7.6 Contents summary information. filescount: number; // Total number of files. filessize: number; // Total files size. @@ -1785,16 +1759,6 @@ export type CoreCourseGetContentsWSModule = { }; }; -/** - * Activity date. - */ -export type CoreCourseGetContentsWSModuleDate = { - label: string; - timestamp: number; - relativeto?: number; // @since 4.1. Relative date timestamp. - dataid?: string; // @since 4.1. ID to identify the text. -}; - /** * Data returned by core_course_get_contents WS. */ diff --git a/src/core/features/reminders/components/components.module.ts b/src/core/features/reminders/components/components.module.ts index a16051728..c99c2b06a 100644 --- a/src/core/features/reminders/components/components.module.ts +++ b/src/core/features/reminders/components/components.module.ts @@ -14,20 +14,26 @@ import { CoreSharedModule } from '@/core/shared.module'; import { NgModule } from '@angular/core'; +import { CoreRemindersDateComponent } from './date/date'; +import { CoreRemindersSetButtonComponent } from './set-button/set-button'; import { CoreRemindersSetReminderCustomComponent } from './set-reminder-custom/set-reminder-custom'; import { CoreRemindersSetReminderMenuComponent } from './set-reminder-menu/set-reminder-menu'; @NgModule({ declarations: [ - CoreRemindersSetReminderMenuComponent, + CoreRemindersDateComponent, + CoreRemindersSetButtonComponent, CoreRemindersSetReminderCustomComponent, + CoreRemindersSetReminderMenuComponent, ], imports: [ CoreSharedModule, ], exports: [ - CoreRemindersSetReminderMenuComponent, + CoreRemindersDateComponent, + CoreRemindersSetButtonComponent, CoreRemindersSetReminderCustomComponent, + CoreRemindersSetReminderMenuComponent, ], }) export class CoreRemindersComponentsModule {} diff --git a/src/core/features/reminders/components/date/date.html b/src/core/features/reminders/components/date/date.html new file mode 100644 index 000000000..4f75b5422 --- /dev/null +++ b/src/core/features/reminders/components/date/date.html @@ -0,0 +1,8 @@ +
+ + {{ label }} {{ readableTime }} +
+ + + diff --git a/src/core/features/reminders/components/date/date.scss b/src/core/features/reminders/components/date/date.scss new file mode 100644 index 000000000..e898db6da --- /dev/null +++ b/src/core/features/reminders/components/date/date.scss @@ -0,0 +1,21 @@ +@import "~theme/globals"; + +:host { + display: flex; + flex-direction: row; +} + +div { + flex-grow: 1; + font-size: 14px; + margin: 0; + align-self: center; + + ion-icon { + @include margin-horizontal(0px, 4px); + } +} + +core-reminders-date + :host { + margin-top: 12px; +} diff --git a/src/core/features/reminders/components/date/date.ts b/src/core/features/reminders/components/date/date.ts new file mode 100644 index 000000000..137d88d4b --- /dev/null +++ b/src/core/features/reminders/components/date/date.ts @@ -0,0 +1,95 @@ +// (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 { CoreReminders } from '@features/reminders/services/reminders'; +import { Component, Input, OnInit } from '@angular/core'; +import { CoreTimeUtils } from '@services/utils/time'; +import { Translate } from '@singletons'; +import { CoreTime } from '@singletons/time'; + +/** + * Component that displays a date to remind. + */ +@Component({ + selector: 'core-reminders-date', + templateUrl: 'date.html', + styleUrls: ['date.scss'], +}) +export class CoreRemindersDateComponent implements OnInit { + + @Input() component?: string; + @Input() instanceId?: number; + @Input() type?: string; + @Input() label = ''; + @Input() time = 0; + @Input() relativeTo = 0; + @Input() title = ''; + @Input() url = ''; + + showReminderButton = false; + timebefore?: number; // Undefined means no reminder has been set. + readableTime = ''; + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + this.readableTime = this.getReadableTime(this.time, this.relativeTo); + + // If not set, button won't be shown. + if (this.component === undefined || this.instanceId === undefined || this.type === undefined) { + return; + } + + const remindersEnabled = CoreReminders.isEnabled(); + this.showReminderButton = remindersEnabled && this.time > CoreTimeUtils.timestamp(); + + if (!this.showReminderButton) { + return; + } + + const reminders = await CoreReminders.getReminders({ + instanceId: this.instanceId, + component: this.component, + type: this.type, + }); + + this.timebefore = reminders[0]?.timebefore; + } + + /** + * Returns the readable time. + * + * @param timestamp Timestamp. + * @param relativeTo Base timestamp if timestamp is relative to this one. + * @return Readable time string. + */ + protected getReadableTime(timestamp: number, relativeTo = 0): string { + if (!relativeTo) { + return CoreTimeUtils.userDate(timestamp * 1000, 'core.strftimedatetime', true); + } + + return Translate.instant( + 'core.course.relativedatessubmissionduedate' + (timestamp > relativeTo ? 'after' : 'before'), + { + $a: { + datediffstr: relativeTo === timestamp ? + '0 ' + Translate.instant('core.secs') : + CoreTime.formatTime(relativeTo - timestamp, 3), + }, + }, + ); + } + +} diff --git a/src/core/features/reminders/components/set-button/set-button.html b/src/core/features/reminders/components/set-button/set-button.html new file mode 100644 index 000000000..fcc693d20 --- /dev/null +++ b/src/core/features/reminders/components/set-button/set-button.html @@ -0,0 +1,4 @@ + + + + diff --git a/src/core/features/reminders/components/set-button/set-button.ts b/src/core/features/reminders/components/set-button/set-button.ts new file mode 100644 index 000000000..d9fa8f439 --- /dev/null +++ b/src/core/features/reminders/components/set-button/set-button.ts @@ -0,0 +1,116 @@ +// (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 { CoreReminderData, CoreReminders, CoreRemindersService } from '@features/reminders/services/reminders'; +import { Component, Input } from '@angular/core'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreRemindersSetReminderMenuComponent } from '../set-reminder-menu/set-reminder-menu'; + +/** + * Component that displays a button to set a reminder. + */ +@Component({ + selector: 'core-reminders-set-button', + templateUrl: 'set-button.html', +}) +export class CoreRemindersSetButtonComponent { + + @Input() component?: string; + @Input() instanceId?: number; + @Input() type?: string; + @Input() label = ''; + @Input() timebefore?: number; + @Input() time = -1; + @Input() title = ''; + @Input() url = ''; + + /** + * Set reminder. + * + * @param ev Click event. + */ + async setReminder(ev: Event): Promise { + if (this.component === undefined || this.instanceId === undefined || this.type === undefined) { + return; + } + + ev.preventDefault(); + ev.stopPropagation(); + + if (this.timebefore === undefined) { + // Set it to the time of the event. + this.saveReminder(0); + + return; + } + + // Open popover. + const reminderTime = await CoreDomUtils.openPopover<{timeBefore: number}>({ + component: CoreRemindersSetReminderMenuComponent, + componentProps: { + initialValue: this.timebefore, + noReminderLabel: 'core.reminders.delete', + }, + event: ev, + }); + + if (reminderTime === undefined) { + // User canceled. + return; + } + + // Save before. + this.saveReminder(reminderTime.timeBefore); + } + + /** + * Save reminder. + * + * @param timebefore Time before the event to fire the notification. + * @return Promise resolved when done. + */ + protected async saveReminder(timebefore: number): Promise { + if (this.component === undefined || this.instanceId === undefined || this.type === undefined) { + return; + } + + if (timebefore === CoreRemindersService.DISABLED) { + // Remove the reminder. + await CoreReminders.removeReminders({ + instanceId: this.instanceId, + component: this.component, + type: this.type, + }); + this.timebefore = undefined; + + return; + } + + this.timebefore = timebefore; + + const reminder: CoreReminderData = { + component: this.component, + instanceId: this.instanceId, + timebefore: this.timebefore, + type: this.type, + title: this.label + ' ' + this.title, + url: this.url, + time: this.time, + }; + + // Save before. + await CoreReminders.addReminder(reminder); + } + +}