From 1e039b0b0f1becfabee21e3640454ac6ffc5f61f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 16 Mar 2021 16:25:03 +0100 Subject: [PATCH 1/4] MOBILE-3654 survey: Add survey activity module --- src/addons/mod/mod.module.ts | 2 + .../survey/components/components.module.ts | 32 ++ .../index/addon-mod-survey-index.html | 152 +++++++ .../mod/survey/components/index/index.scss | 25 ++ .../mod/survey/components/index/index.ts | 250 +++++++++++ src/addons/mod/survey/lang.json | 10 + src/addons/mod/survey/pages/index/index.html | 19 + src/addons/mod/survey/pages/index/index.ts | 30 ++ .../mod/survey/services/database/survey.ts | 68 +++ .../survey/services/handlers/index-link.ts | 32 ++ .../mod/survey/services/handlers/list-link.ts | 32 ++ .../mod/survey/services/handlers/module.ts | 84 ++++ .../mod/survey/services/handlers/prefetch.ts | 114 +++++ .../mod/survey/services/handlers/sync-cron.ts | 43 ++ .../mod/survey/services/survey-helper.ts | 138 ++++++ .../mod/survey/services/survey-offline.ts | 151 +++++++ src/addons/mod/survey/services/survey-sync.ts | 257 ++++++++++++ src/addons/mod/survey/services/survey.ts | 393 ++++++++++++++++++ src/addons/mod/survey/survey-lazy.module.ts | 39 ++ src/addons/mod/survey/survey.module.ts | 75 ++++ src/core/features/compile/services/compile.ts | 4 +- 21 files changed, 1948 insertions(+), 2 deletions(-) create mode 100644 src/addons/mod/survey/components/components.module.ts create mode 100644 src/addons/mod/survey/components/index/addon-mod-survey-index.html create mode 100644 src/addons/mod/survey/components/index/index.scss create mode 100644 src/addons/mod/survey/components/index/index.ts create mode 100644 src/addons/mod/survey/lang.json create mode 100644 src/addons/mod/survey/pages/index/index.html create mode 100644 src/addons/mod/survey/pages/index/index.ts create mode 100644 src/addons/mod/survey/services/database/survey.ts create mode 100644 src/addons/mod/survey/services/handlers/index-link.ts create mode 100644 src/addons/mod/survey/services/handlers/list-link.ts create mode 100644 src/addons/mod/survey/services/handlers/module.ts create mode 100644 src/addons/mod/survey/services/handlers/prefetch.ts create mode 100644 src/addons/mod/survey/services/handlers/sync-cron.ts create mode 100644 src/addons/mod/survey/services/survey-helper.ts create mode 100644 src/addons/mod/survey/services/survey-offline.ts create mode 100644 src/addons/mod/survey/services/survey-sync.ts create mode 100644 src/addons/mod/survey/services/survey.ts create mode 100644 src/addons/mod/survey/survey-lazy.module.ts create mode 100644 src/addons/mod/survey/survey.module.ts diff --git a/src/addons/mod/mod.module.ts b/src/addons/mod/mod.module.ts index 95fdd0b31..60fea5102 100644 --- a/src/addons/mod/mod.module.ts +++ b/src/addons/mod/mod.module.ts @@ -27,6 +27,7 @@ import { AddonModResourceModule } from './resource/resource.module'; import { AddonModUrlModule } from './url/url.module'; import { AddonModLtiModule } from './lti/lti.module'; import { AddonModH5PActivityModule } from './h5pactivity/h5pactivity.module'; +import { AddonModSurveyModule } from './survey/survey.module'; @NgModule({ declarations: [], @@ -44,6 +45,7 @@ import { AddonModH5PActivityModule } from './h5pactivity/h5pactivity.module'; AddonModImscpModule, AddonModLtiModule, AddonModH5PActivityModule, + AddonModSurveyModule, ], providers: [], exports: [], diff --git a/src/addons/mod/survey/components/components.module.ts b/src/addons/mod/survey/components/components.module.ts new file mode 100644 index 000000000..22bde845b --- /dev/null +++ b/src/addons/mod/survey/components/components.module.ts @@ -0,0 +1,32 @@ +// (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 { NgModule } from '@angular/core'; +import { AddonModSurveyIndexComponent } from './index/index'; +import { CoreSharedModule } from '@/core/shared.module'; +import { CoreCourseComponentsModule } from '@features/course/components/components.module'; + +@NgModule({ + declarations: [ + AddonModSurveyIndexComponent, + ], + imports: [ + CoreSharedModule, + CoreCourseComponentsModule, + ], + exports: [ + AddonModSurveyIndexComponent, + ], +}) +export class AddonModSurveyComponentsModule {} diff --git a/src/addons/mod/survey/components/index/addon-mod-survey-index.html b/src/addons/mod/survey/components/index/addon-mod-survey-index.html new file mode 100644 index 000000000..89f5cad45 --- /dev/null +++ b/src/addons/mod/survey/components/index/addon-mod-survey-index.html @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

{{ 'addon.mod_survey.surveycompletednograph' | translate }}

+ + + {{ 'addon.mod_survey.results' | translate }} + +
+ + + + + + {{ 'core.hasdatatosync' | translate: {$a: moduleName} }} + + + + +
+ + + + + +

{{ question.text }}

+ + {{ 'addon.mod_survey.responses' | translate }} + + {{ option }} + + + +

{{ question.intro }}

+
+
+ + + + + + + + + {{question.num}}. {{ question.text }} + + + + + + + + + + + + + {{ 'core.choose' | translate }} + + {{option}} + + + + + + + + + + + + + {{question.num}}. {{ question.text }} + + + + + + + {{option}} + + + + + + + + + {{question.num}}. {{ question.text }} + + + + + + + +
+
+ + + + + {{ 'core.submit' | translate }} + + + +
+ +
diff --git a/src/addons/mod/survey/components/index/index.scss b/src/addons/mod/survey/components/index/index.scss new file mode 100644 index 000000000..261e20841 --- /dev/null +++ b/src/addons/mod/survey/components/index/index.scss @@ -0,0 +1,25 @@ +:host { + --grid-background: var(--white); + --even-background: var(--gray-light); + + .option-name { + font-size: 14px; + } + + .addon-mod_survey-question { + border-top: 1px solid var(--gray); + } + + ion-row { + background-color: var(--grid-background); + } + + .even { + background-color: var(--even-background); + } +} + +:host-context(body.dark) { + --grid-background: var(--black); + --even-background: var(--gray-darker); +} diff --git a/src/addons/mod/survey/components/index/index.ts b/src/addons/mod/survey/components/index/index.ts new file mode 100644 index 000000000..38db91dd8 --- /dev/null +++ b/src/addons/mod/survey/components/index/index.ts @@ -0,0 +1,250 @@ +// (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 { Component, OnInit, Optional } from '@angular/core'; +import { CoreIonLoadingElement } from '@classes/ion-loading'; +import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; +import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; +import { CoreCourse } from '@features/course/services/course'; +import { IonContent } from '@ionic/angular'; +import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreTextUtils } from '@services/utils/text'; +import { Translate } from '@singletons'; +import { CoreEvents } from '@singletons/events'; +import { AddonModSurveyPrefetchHandler } from '../../services/handlers/prefetch'; +import { + AddonModSurveyProvider, + AddonModSurveySurvey, + AddonModSurvey, + AddonModSurveySubmitAnswerData, +} from '../../services/survey'; +import { AddonModSurveyHelper, AddonModSurveyQuestionFormatted } from '../../services/survey-helper'; +import { AddonModSurveyOffline } from '../../services/survey-offline'; +import { AddonModSurveyAutoSyncData, AddonModSurveySync, AddonModSurveySyncResult } from '../../services/survey-sync'; + +/** + * Component that displays a survey. + */ +@Component({ + selector: 'addon-mod-survey-index', + templateUrl: 'addon-mod-survey-index.html', + styleUrls: ['index.scss'], +}) +export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit { + + component = AddonModSurveyProvider.COMPONENT; + moduleName = 'survey'; + + survey?: AddonModSurveySurvey; + questions: AddonModSurveyQuestionFormatted[] = []; + answers: Record = {}; + + protected currentUserId?: number; + + constructor( + protected content?: IonContent, + @Optional() courseContentsPage?: CoreCourseContentsPage, + ) { + super('AddonModSurveyIndexComponent', content, courseContentsPage); + } + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + super.ngOnInit(); + + this.currentUserId = CoreSites.getCurrentSiteUserId(); + + await this.loadContent(false, true); + + try { + await AddonModSurvey.logView(this.survey!.id, this.survey!.name); + CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); + } catch { + // Ignore errors. Just don't check Module completion. + } + } + + /** + * Perform the invalidate content function. + * + * @return Resolved when done. + */ + protected async invalidateContent(): Promise { + const promises: Promise[] = []; + + promises.push(AddonModSurvey.invalidateSurveyData(this.courseId)); + if (this.survey) { + promises.push(AddonModSurvey.invalidateQuestions(this.survey.id)); + } + + await Promise.all(promises); + } + + /** + * Compares sync event data with current data to check if refresh content is needed. + * + * @param syncEventData Data receiven on sync observer. + * @return True if refresh is needed, false otherwise. + */ + protected isRefreshSyncNeeded(syncEventData: AddonModSurveyAutoSyncData): boolean { + if (this.survey && syncEventData.surveyId == this.survey.id && syncEventData.userId == this.currentUserId) { + return true; + } + + return false; + } + + /** + * Download survey contents. + * + * @param refresh If it's refreshing content. + * @param sync If it should try to sync. + * @param showErrors If show errors to the user of hide them. + * @return Promise resolved when done. + */ + protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise { + try { + this.survey = await AddonModSurvey.getSurvey(this.courseId, this.module.id); + + this.description = this.survey.intro; + this.dataRetrieved.emit(this.survey); + + if (sync) { + // Try to synchronize the survey. + const answersSent = await this.syncActivity(showErrors); + if (answersSent) { + // Answers were sent, update the survey. + this.survey = await AddonModSurvey.getSurvey(this.courseId, this.module.id); + } + } + + // Check if there are answers stored in offline. + this.hasOffline = this.survey.surveydone + ? false + : await AddonModSurveyOffline.hasAnswers(this.survey.id); + + if (!this.survey.surveydone && !this.hasOffline) { + await this.fetchQuestions(); + } + } finally { + this.fillContextMenu(refresh); + } + } + + /** + * Convenience function to get survey questions. + * + * @return Promise resolved when done. + */ + protected async fetchQuestions(): Promise { + const questions = await AddonModSurvey.getQuestions(this.survey!.id, { cmId: this.module.id }); + + this.questions = AddonModSurveyHelper.formatQuestions(questions); + + // Init answers object. + this.questions.forEach((question) => { + if (question.name) { + const isTextArea = question.multiArray && question.multiArray.length === 0 && question.type === 0; + this.answers[question.name] = question.required ? '-1' : (isTextArea ? '' : '0'); + } + + if (question.multiArray && !question.multiArray.length && question.parent === 0 && question.type > 0) { + // Options shown in a select. Remove all HTML. + question.optionsArray = question.optionsArray?.map((option) => CoreTextUtils.cleanTags(option)); + } + }); + } + + /** + * Check if answers are valid to be submitted. + * + * @return If answers are valid + */ + isValidResponse(): boolean { + return !this.questions.some((question) => question.required && question.name && + (question.type === 0 ? this.answers[question.name] == '' : parseInt(this.answers[question.name], 10) === -1)); + } + + /** + * Save options selected. + */ + async submit(): Promise { + let modal: CoreIonLoadingElement | undefined; + + try { + await CoreDomUtils.showConfirm(Translate.instant('core.areyousure')); + + const answers: AddonModSurveySubmitAnswerData[] = []; + modal = await CoreDomUtils.showModalLoading('core.sending', true); + + for (const x in this.answers) { + answers.push({ + key: x, + value: this.answers[x], + }); + } + + const online = await AddonModSurvey.submitAnswers(this.survey!.id, this.survey!.name, this.courseId, answers); + + CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: this.moduleName }); + + if (online && this.isPrefetched()) { + // The survey is downloaded, update the data. + try { + await AddonModSurveySync.prefetchAfterUpdate( + AddonModSurveyPrefetchHandler.instance, + this.module, + this.courseId, + ); + + // Update the view. + this.showLoadingAndFetch(false, false); + } catch { + // Prefetch failed, refresh the data. + await this.showLoadingAndRefresh(false); + } + } else { + // Not downloaded, refresh the data. + await this.showLoadingAndRefresh(false); + } + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'addon.mod_survey.cannotsubmitsurvey', true); + } finally { + modal?.dismiss(); + } + } + + /** + * Performs the sync of the activity. + * + * @return Promise resolved when done. + */ + protected async sync(): Promise { + await AddonModSurveySync.syncSurvey(this.survey!.id, this.currentUserId); + } + + /** + * Checks if sync has succeed from result sync data. + * + * @param result Data returned on the sync function. + * @return If suceed or not. + */ + protected hasSyncSucceed(result: AddonModSurveySyncResult): boolean { + return result.answersSent; + } + +} diff --git a/src/addons/mod/survey/lang.json b/src/addons/mod/survey/lang.json new file mode 100644 index 000000000..9ccaff870 --- /dev/null +++ b/src/addons/mod/survey/lang.json @@ -0,0 +1,10 @@ +{ + "cannotsubmitsurvey": "Sorry, there was a problem submitting your survey. Please try again.", + "errorgetsurvey": "Error getting survey data.", + "ifoundthat": "I found that", + "ipreferthat": "I prefer that", + "modulenameplural": "Surveys", + "responses": "Responses", + "results": "Results", + "surveycompletednograph": "You have completed this survey." +} \ No newline at end of file diff --git a/src/addons/mod/survey/pages/index/index.html b/src/addons/mod/survey/pages/index/index.html new file mode 100644 index 000000000..3ccf0efe9 --- /dev/null +++ b/src/addons/mod/survey/pages/index/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/addons/mod/survey/pages/index/index.ts b/src/addons/mod/survey/pages/index/index.ts new file mode 100644 index 000000000..68d8aa521 --- /dev/null +++ b/src/addons/mod/survey/pages/index/index.ts @@ -0,0 +1,30 @@ +// (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 { Component, ViewChild } from '@angular/core'; +import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page'; +import { AddonModSurveyIndexComponent } from '../../components/index'; + +/** + * Page that displays a survey. + */ +@Component({ + selector: 'page-addon-mod-survey-index', + templateUrl: 'index.html', +}) +export class AddonModSurveyIndexPage extends CoreCourseModuleMainActivityPage { + + @ViewChild(AddonModSurveyIndexComponent) activityComponent?: AddonModSurveyIndexComponent; + +} diff --git a/src/addons/mod/survey/services/database/survey.ts b/src/addons/mod/survey/services/database/survey.ts new file mode 100644 index 000000000..060ad0394 --- /dev/null +++ b/src/addons/mod/survey/services/database/survey.ts @@ -0,0 +1,68 @@ +// (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 { CoreSiteSchema } from '@services/sites'; + +/** + * Database variables for AddonModSurveyOfflineProvider. + */ +export const SURVEY_TABLE = 'addon_mod_survey_answers'; +export const ADDON_MOD_SURVEY_OFFLINE_SITE_SCHEMA: CoreSiteSchema = { + name: 'AddonModSurveyOfflineProvider', + version: 1, + tables: [ + { + name: SURVEY_TABLE, + columns: [ + { + name: 'surveyid', + type: 'INTEGER', + }, + { + name: 'name', + type: 'TEXT', + }, + { + name: 'courseid', + type: 'INTEGER', + }, + { + name: 'userid', + type: 'INTEGER', + }, + { + name: 'answers', + type: 'TEXT', + }, + { + name: 'timecreated', + type: 'INTEGER', + }, + ], + primaryKeys: ['surveyid', 'userid'], + }, + ], +}; + +/** + * Survey offline answers. + */ +export type AddonModSurveyAnswersDBRecord = { + surveyid: number; + userid: number; + name: string; + courseid: number; + answers: string; + timecreated: number; +}; diff --git a/src/addons/mod/survey/services/handlers/index-link.ts b/src/addons/mod/survey/services/handlers/index-link.ts new file mode 100644 index 000000000..8fa2d56d8 --- /dev/null +++ b/src/addons/mod/survey/services/handlers/index-link.ts @@ -0,0 +1,32 @@ +// (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 { Injectable } from '@angular/core'; +import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to treat links to survey. + */ +@Injectable( { providedIn: 'root' }) +export class AddonModSurveyIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler { + + name = 'AddonModSurveyLinkHandler'; + + constructor() { + super('AddonModSurvey', 'survey'); + } + +} +export const AddonModSurveyIndexLinkHandler = makeSingleton(AddonModSurveyIndexLinkHandlerService); diff --git a/src/addons/mod/survey/services/handlers/list-link.ts b/src/addons/mod/survey/services/handlers/list-link.ts new file mode 100644 index 000000000..85ffc4c1d --- /dev/null +++ b/src/addons/mod/survey/services/handlers/list-link.ts @@ -0,0 +1,32 @@ +// (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 { Injectable } from '@angular/core'; +import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to treat links to survey list page. + */ +@Injectable( { providedIn: 'root' }) +export class AddonModSurveyListLinkHandlerService extends CoreContentLinksModuleListHandler { + + name = 'AddonModSurveyListLinkHandler'; + + constructor() { + super('AddonModSurvey', 'survey'); + } + +} +export const AddonModSurveyListLinkHandler = makeSingleton(AddonModSurveyListLinkHandlerService); diff --git a/src/addons/mod/survey/services/handlers/module.ts b/src/addons/mod/survey/services/handlers/module.ts new file mode 100644 index 000000000..704249350 --- /dev/null +++ b/src/addons/mod/survey/services/handlers/module.ts @@ -0,0 +1,84 @@ +// (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 { CoreConstants } from '@/core/constants'; +import { Injectable, Type } from '@angular/core'; +import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course'; +import { CoreCourseModule } from '@features/course/services/course-helper'; +import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; +import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; +import { makeSingleton } from '@singletons'; +import { AddonModSurveyIndexComponent } from '../../components/index'; + +/** + * Handler to support survey modules. + */ +@Injectable( { providedIn: 'root' }) +export class AddonModSurveyModuleHandlerService implements CoreCourseModuleHandler { + + static readonly PAGE_NAME = 'mod_survey'; + + name = 'AddonModSurvey'; + modName = 'survey'; + + supportedFeatures = { + [CoreConstants.FEATURE_GROUPS]: true, + [CoreConstants.FEATURE_GROUPINGS]: true, + [CoreConstants.FEATURE_MOD_INTRO]: true, + [CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true, + [CoreConstants.FEATURE_COMPLETION_HAS_RULES]: true, + [CoreConstants.FEATURE_GRADE_HAS_GRADE]: false, + [CoreConstants.FEATURE_GRADE_OUTCOMES]: false, + [CoreConstants.FEATURE_BACKUP_MOODLE2]: true, + [CoreConstants.FEATURE_SHOW_DESCRIPTION]: true, + }; + + /** + * @inheritdoc + */ + async isEnabled(): Promise { + return true; + } + + /** + * @inheritdoc + */ + getData( + module: CoreCourseAnyModuleData, + ): CoreCourseModuleHandlerData { + return { + icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined), + title: module.name, + class: 'addon-mod_survey-handler', + showDownloadButton: true, + action: (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions) => { + options = options || {}; + options.params = options.params || {}; + Object.assign(options.params, { module }); + const routeParams = '/' + courseId + '/' + module.id; + + CoreNavigator.navigateToSitePath(AddonModSurveyModuleHandlerService.PAGE_NAME + routeParams, options); + }, + }; + } + + /** + * @inheritdoc + */ + async getMainComponent(): Promise> { + return AddonModSurveyIndexComponent; + } + +} +export const AddonModSurveyModuleHandler = makeSingleton(AddonModSurveyModuleHandlerService); diff --git a/src/addons/mod/survey/services/handlers/prefetch.ts b/src/addons/mod/survey/services/handlers/prefetch.ts new file mode 100644 index 000000000..c856f5656 --- /dev/null +++ b/src/addons/mod/survey/services/handlers/prefetch.ts @@ -0,0 +1,114 @@ +// (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 { Injectable } from '@angular/core'; +import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler'; +import { CoreCourseAnyModuleData } from '@features/course/services/course'; +import { CoreFilepool } from '@services/filepool'; +import { CoreSitesReadingStrategy } from '@services/sites'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreWSExternalFile } from '@services/ws'; +import { makeSingleton } from '@singletons'; +import { AddonModSurvey, AddonModSurveyProvider } from '../survey'; +import { AddonModSurveySync, AddonModSurveySyncResult } from '../survey-sync'; + +/** + * Handler to prefetch surveys. + */ +@Injectable( { providedIn: 'root' }) +export class AddonModSurveyPrefetchHandlerService extends CoreCourseActivityPrefetchHandlerBase { + + name = 'AddonModSurvey'; + modName = 'survey'; + component = AddonModSurveyProvider.COMPONENT; + updatesNames = /^configuration$|^.*files$|^answers$/; + + /** + * @inheritdoc + */ + async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise { + const survey = await CoreUtils.ignoreErrors(AddonModSurvey.getSurvey(courseId, module.id)); + + return this.getIntroFilesFromInstance(module, survey); + } + + /** + * @inheritdoc + */ + async invalidateContent(moduleId: number, courseId: number): Promise { + return AddonModSurvey.invalidateContent(moduleId, courseId); + } + + /** + * @inheritdoc + */ + async invalidateModule(module: CoreCourseAnyModuleData, courseId: number): Promise { + await AddonModSurvey.invalidateSurveyData(courseId); + } + + /** + * @inheritdoc + */ + async isEnabled(): Promise { + return true; + } + + /** + * @inheritdoc + */ + prefetch(module: CoreCourseAnyModuleData, courseId?: number): Promise { + return this.prefetchPackage(module, courseId, this.prefetchSurvey.bind(this, module, courseId)); + } + + /** + * Prefetch a survey. + * + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param siteId SiteId or current site. + * @return Promise resolved when done. + */ + protected async prefetchSurvey(module: CoreCourseAnyModuleData, courseId: number, siteId: string): Promise { + const survey = await AddonModSurvey.getSurvey(courseId, module.id, { + readingStrategy: CoreSitesReadingStrategy.OnlyNetwork, + siteId, + }); + + const promises: Promise[] = []; + const files = this.getIntroFilesFromInstance(module, survey); + + // Prefetch files. + promises.push(CoreFilepool.addFilesToQueue(siteId, files, AddonModSurveyProvider.COMPONENT, module.id)); + + // If survey isn't answered, prefetch the questions. + if (!survey.surveydone) { + promises.push(AddonModSurvey.getQuestions(survey.id, { + cmId: module.id, + readingStrategy: CoreSitesReadingStrategy.OnlyNetwork, + siteId, + })); + } + + await Promise.all(promises); + } + + /** + * @inheritdoc + */ + sync(module: CoreCourseAnyModuleData, courseId: number, siteId?: string): Promise { + return AddonModSurveySync.syncSurvey(module.instance!, undefined, siteId); + } + +} +export const AddonModSurveyPrefetchHandler = makeSingleton(AddonModSurveyPrefetchHandlerService); diff --git a/src/addons/mod/survey/services/handlers/sync-cron.ts b/src/addons/mod/survey/services/handlers/sync-cron.ts new file mode 100644 index 000000000..bad10bf57 --- /dev/null +++ b/src/addons/mod/survey/services/handlers/sync-cron.ts @@ -0,0 +1,43 @@ +// (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 { Injectable } from '@angular/core'; +import { CoreCronHandler } from '@services/cron'; +import { makeSingleton } from '@singletons'; +import { AddonModSurveySync } from '../survey-sync'; + +/** + * Synchronization cron handler. + */ +@Injectable( { providedIn: 'root' }) +export class AddonModSurveySyncCronHandlerService implements CoreCronHandler { + + name = 'AddonModSurveySyncCronHandler'; + + /** + * @inheritdoc + */ + async execute(siteId?: string, force?: boolean): Promise { + await AddonModSurveySync.syncAllSurveys(siteId, force); + } + + /** + * @inheritdoc + */ + getInterval(): number { + return AddonModSurveySync.syncInterval; + } + +} +export const AddonModSurveySyncCronHandler = makeSingleton(AddonModSurveySyncCronHandlerService); diff --git a/src/addons/mod/survey/services/survey-helper.ts b/src/addons/mod/survey/services/survey-helper.ts new file mode 100644 index 000000000..352ad6487 --- /dev/null +++ b/src/addons/mod/survey/services/survey-helper.ts @@ -0,0 +1,138 @@ +// (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 { Injectable } from '@angular/core'; +import { makeSingleton, Translate } from '@singletons'; +import { AddonModSurveyQuestion } from './survey'; + +/** + * Service that provides helper functions for surveys. + */ +@Injectable( { providedIn: 'root' }) +export class AddonModSurveyHelperProvider { + + /** + * Turns a string with values separated by commas into an array. + * + * @param value Value to convert. + * @return Array. + */ + protected commaStringToArray(value: string | string[]): string[] { + if (typeof value == 'string') { + if (value.length > 0) { + return value.split(','); + } + + return []; + } + + return value; + } + + /** + * Gets the parent questions and puts them in an object: ID -> question. + * + * @param questions Questions. + * @return Object with parent questions. + */ + protected getParentQuestions(questions: AddonModSurveyQuestion[]): {[id: number]: AddonModSurveyQuestion} { + const parents: { [id: number]: AddonModSurveyQuestion } = {}; + + questions.forEach((question) => { + if (question.parent === 0) { + parents[question.id] = question; + } + }); + + return parents; + } + + /** + * Format a questions list, turning "multi" and "options" strings into arrays and adding the properties + * 'num' and 'name'. + * + * @param questions Questions. + * @return Promise resolved with the formatted questions. + */ + formatQuestions(questions: AddonModSurveyQuestion[]): AddonModSurveyQuestionFormatted[] { + const strIPreferThat = Translate.instant('addon.mod_survey.ipreferthat'); + const strIFoundThat = Translate.instant('addon.mod_survey.ifoundthat'); + const strChoose = Translate.instant('core.choose'); + + const formatted: AddonModSurveyQuestionFormatted[] = []; + const parents = this.getParentQuestions(questions); + + let num = 1; + + questions.forEach((question) => { + // Copy the object to prevent modifying the original. + const q1: AddonModSurveyQuestionFormatted = Object.assign({}, question); + const parent = parents[q1.parent]; + + // Turn multi and options into arrays. + q1.multiArray = this.commaStringToArray(q1.multi); + q1.optionsArray = this.commaStringToArray(q1.options); + + if (parent) { + // It's a sub-question. + q1.required = true; + + if (parent.type === 1 || parent.type === 2) { + // One answer question. Set its name and add it to the returned array. + q1.name = 'q' + (parent.type == 2 ? 'P' : '') + q1.id; + q1.num = num++; + } else { + // Two answers per question (COLLES P&A). We'll add two questions. + const q2 = Object.assign({}, q1); + + q1.text = strIPreferThat + ' ' + q1.text; + q1.name = 'qP' + q1.id; + q1.num = num++; + formatted.push(q1); + + q2.text = strIFoundThat + ' ' + q2.text; + q2.name = 'q' + q1.id; + q2.num = num++; + formatted.push(q2); + + return; + } + } else if (q1.multiArray && q1.multiArray.length === 0) { + // It's a single question. + q1.name = 'q' + q1.id; + q1.num = num++; + if (q1.type > 0) { // Add "choose" option since this question is not required. + q1.optionsArray.unshift(strChoose); + } + } + + formatted.push(q1); + }); + + return formatted; + } + +} +export const AddonModSurveyHelper = makeSingleton(AddonModSurveyHelperProvider); + +/** + * Survey question with some calculated data. + */ +export type AddonModSurveyQuestionFormatted = AddonModSurveyQuestion & { + required?: boolean; // Calculated in the app. Whether the question is required. + name?: string; // Calculated in the app. The name of the question. + num?: number; // Calculated in the app. Number of the question. + multiArray?: string[]; // Subquestions ids, converted to an array. + optionsArray?: string[]; // Question options, converted to an array. +}; diff --git a/src/addons/mod/survey/services/survey-offline.ts b/src/addons/mod/survey/services/survey-offline.ts new file mode 100644 index 000000000..0a0f468eb --- /dev/null +++ b/src/addons/mod/survey/services/survey-offline.ts @@ -0,0 +1,151 @@ +// (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 { Injectable } from '@angular/core'; +import { CoreSites } from '@services/sites'; +import { CoreTextUtils } from '@services/utils/text'; +import { makeSingleton } from '@singletons'; +import { AddonModSurveyAnswersDBRecord, SURVEY_TABLE } from './database/survey'; +import { AddonModSurveySubmitAnswerData } from './survey'; + +/** + * Service to handle Offline survey. + */ +@Injectable( { providedIn: 'root' }) +export class AddonModSurveyOfflineProvider { + + /** + * Delete a survey answers. + * + * @param surveyId Survey ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User the answers belong to. If not defined, current user in site. + * @return Promise resolved if deleted, rejected if failure. + */ + async deleteSurveyAnswers(surveyId: number, siteId?: string, userId?: number): Promise { + const site = await CoreSites.getSite(siteId); + userId = userId || site.getUserId(); + + await site.getDb().deleteRecords(SURVEY_TABLE, { surveyid: surveyId, userid: userId }); + } + + /** + * Get all the stored data from all the surveys. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with answers. + */ + async getAllData(siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + const entries = await site.getDb().getAllRecords(SURVEY_TABLE); + + return entries.map((entry) => Object.assign(entry, { + answers: CoreTextUtils.parseJSON(entry.answers), + })); + } + + /** + * Get a survey stored answers. + * + * @param surveyId Survey ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User the answers belong to. If not defined, current user in site. + * @return Promise resolved with the answers. + */ + async getSurveyAnswers(surveyId: number, siteId?: string, userId?: number): Promise { + try { + const entry = await this.getSurveyData(surveyId, siteId, userId); + + return entry.answers || []; + } catch { + return []; + } + } + + /** + * Get a survey stored data. + * + * @param surveyId Survey ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User the answers belong to. If not defined, current user in site. + * @return Promise resolved with the data. + */ + async getSurveyData(surveyId: number, siteId?: string, userId?: number): Promise { + const site = await CoreSites.getSite(siteId); + userId = userId || site.getUserId(); + + const entry = await site.getDb().getRecord( + SURVEY_TABLE, + { surveyid: surveyId, userid: userId }, + ); + + return Object.assign(entry, { + answers: CoreTextUtils.parseJSON(entry.answers), + }); + } + + /** + * Check if there are offline answers to send. + * + * @param surveyId Survey ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User the answers belong to. If not defined, current user in site. + * @return Promise resolved with boolean: true if has offline answers, false otherwise. + */ + async hasAnswers(surveyId: number, siteId?: string, userId?: number): Promise { + const answers = await this.getSurveyAnswers(surveyId, siteId, userId); + + return !!answers.length; + } + + /** + * Save answers to be sent later. + * + * @param surveyId Survey ID. + * @param name Survey name. + * @param courseId Course ID the survey belongs to. + * @param answers Answers. + * @param siteId Site ID. If not defined, current site. + * @param userId User the answers belong to. If not defined, current user in site. + * @return Promise resolved if stored, rejected if failure. + */ + async saveAnswers( + surveyId: number, + name: string, + courseId: number, + answers: AddonModSurveySubmitAnswerData[], + siteId?: string, + userId?: number, + ): Promise { + const site = await CoreSites.getSite(siteId); + userId = userId || site.getUserId(); + + const entry: AddonModSurveyAnswersDBRecord = { + surveyid: surveyId, + name: name, + courseid: courseId, + userid: userId, + answers: JSON.stringify(answers), + timecreated: new Date().getTime(), + }; + + await site.getDb().insertRecord(SURVEY_TABLE, entry); + } + +} +export const AddonModSurveyOffline = makeSingleton(AddonModSurveyOfflineProvider); + +export type AddonModSurveyAnswersDBRecordFormatted = Omit & { + answers: AddonModSurveySubmitAnswerData[]; +}; diff --git a/src/addons/mod/survey/services/survey-sync.ts b/src/addons/mod/survey/services/survey-sync.ts new file mode 100644 index 000000000..c2c10cb39 --- /dev/null +++ b/src/addons/mod/survey/services/survey-sync.ts @@ -0,0 +1,257 @@ +// (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 { Injectable } from '@angular/core'; +import { CoreNetworkError } from '@classes/errors/network-error'; +import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync'; +import { CoreCourse } from '@features/course/services/course'; +import { CoreCourseLogHelper } from '@features/course/services/log-helper'; +import { CoreApp } from '@services/app'; +import { CoreSites } from '@services/sites'; +import { CoreTextUtils } from '@services/utils/text'; +import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton, Translate } from '@singletons'; +import { CoreEvents } from '@singletons/events'; +import { AddonModSurveyPrefetchHandler } from './handlers/prefetch'; +import { AddonModSurvey, AddonModSurveyProvider } from './survey'; +import { AddonModSurveyAnswersDBRecordFormatted, AddonModSurveyOffline } from './survey-offline'; + +/** + * Service to sync surveys. + */ +@Injectable( { providedIn: 'root' }) +export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvider { + + static readonly AUTO_SYNCED = 'addon_mod_survey_autom_synced'; + + protected componentTranslate: string; + + constructor() { + super('AddonModSurveySyncProvider'); + this.componentTranslate = CoreCourse.translateModuleName('survey'); + } + + /** + * Get the ID of a survey sync. + * + * @param surveyId Survey ID. + * @param userId User the answers belong to. + * @return Sync ID. + * @protected + */ + getSyncId(surveyId: number, userId: number): string { + return surveyId + '#' + userId; + } + + /** + * Try to synchronize all the surveys in a certain site or in all sites. + * + * @param siteId Site ID to sync. If not defined, sync all sites. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. + */ + syncAllSurveys(siteId?: string, force?: boolean): Promise { + return this.syncOnSites('all surveys', this.syncAllSurveysFunc.bind(this, !!force), siteId); + } + + /** + * Sync all pending surveys on a site. + * + * @param force Wether to force sync not depending on last execution. + * @param siteId Site ID to sync. + * @param Promise resolved if sync is successful, rejected if sync fails. + */ + protected async syncAllSurveysFunc(force: boolean, siteId: string): Promise { + // Get all survey answers pending to be sent in the site. + const entries = await AddonModSurveyOffline.getAllData(siteId); + + // Sync all surveys. + const promises = entries.map(async (entry) => { + const result = await (force + ? this.syncSurvey(entry.surveyid, entry.userid, siteId) + : this.syncSurveyIfNeeded(entry.surveyid, entry.userid, siteId)); + + if (result && result.answersSent) { + // Sync successful, send event. + CoreEvents.trigger(AddonModSurveySyncProvider.AUTO_SYNCED, { + surveyId: entry.surveyid, + userId: entry.userid, + warnings: result.warnings, + }, siteId); + } + }); + + await Promise.all(promises); + } + + /** + * Sync a survey only if a certain time has passed since the last time. + * + * @param surveyId Survey ID. + * @param userId User the answers belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the survey is synced or if it doesn't need to be synced. + */ + async syncSurveyIfNeeded(surveyId: number, userId: number, siteId?: string): Promise { + const syncId = this.getSyncId(surveyId, userId); + + const needed = await this.isSyncNeeded(syncId, siteId); + if (needed) { + return this.syncSurvey(surveyId, userId, siteId); + } + } + + /** + * Synchronize a survey. + * + * @param surveyId Survey ID. + * @param userId User the answers belong to. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. + */ + async syncSurvey(surveyId: number, userId?: number, siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + siteId = site.getId(); + userId = userId || site.getUserId(); + + const syncId = this.getSyncId(surveyId, userId); + + if (this.isSyncing(syncId, siteId)) { + // There's already a sync ongoing for this site, return the promise. + return this.getOngoingSync(syncId, siteId)!; + } + + this.logger.debug(`Try to sync survey '${surveyId}' for user '${userId}'`); + + // Get offline events. + const syncPromise = this.performSyncSurvey(surveyId, userId, siteId); + + return this.addOngoingSync(syncId, syncPromise, siteId); + } + + /** + * Perform the survey sync. + * + * @param surveyId Survey ID. + * @param userId User the answers belong to. If not defined, current user. + * @param siteId Site ID. + * @return Promise resolved if sync is successful, rejected otherwise. + */ + protected async performSyncSurvey(surveyId: number, userId: number, siteId: string): Promise { + const result: AddonModSurveySyncResult = { + warnings: [], + answersSent: false, + }; + + // Sync offline logs. + CoreUtils.ignoreErrors(CoreCourseLogHelper.syncActivity(AddonModSurveyProvider.COMPONENT, surveyId, siteId)); + + let answersNumber = 0; + let data: AddonModSurveyAnswersDBRecordFormatted | undefined; + try { + // Get answers to be sent. + data = await AddonModSurveyOffline.getSurveyData(surveyId, siteId, userId); + + answersNumber = data.answers.length; + } catch { + // Ignore errors. + } + + if (answersNumber > 0 && data) { + if (!CoreApp.isOnline()) { + // Cannot sync in offline. + throw new CoreNetworkError(); + } + + result.courseId = data.courseid; + + // Send the answers. + try { + await AddonModSurvey.submitAnswersOnline(surveyId, data.answers, siteId); + + result.answersSent = true; + + // Answers sent, delete them. + await AddonModSurveyOffline.deleteSurveyAnswers(surveyId, siteId, userId); + } catch (error) { + if (!CoreUtils.isWebServiceError(error)) { + // Local error, reject. + throw error; + } + + // The WebService has thrown an error, this means that answers cannot be submitted. Delete them. + result.answersSent = true; + + await AddonModSurveyOffline.deleteSurveyAnswers(surveyId, siteId, userId); + + // Answers deleted, add a warning. + result.warnings.push(Translate.instant('core.warningofflinedatadeleted', { + component: this.componentTranslate, + name: data.name, + error: CoreTextUtils.getErrorMessageFromError(error), + })); + } + + if (result.courseId) { + await AddonModSurvey.invalidateSurveyData(result.courseId, siteId); + + // Data has been sent to server, update survey data. + const module = await CoreCourse.getModuleBasicInfoByInstance(surveyId, 'survey', siteId); + + CoreUtils.ignoreErrors( + this.prefetchAfterUpdate(AddonModSurveyPrefetchHandler.instance, module, result.courseId, undefined, siteId), + ); + } + } + + const syncId = this.getSyncId(surveyId, userId); + // Sync finished, set sync time. + CoreUtils.ignoreErrors(this.setSyncTime(syncId, siteId)); + + return result; + } + +} +export const AddonModSurveySync = makeSingleton(AddonModSurveySyncProvider); + +declare module '@singletons/events' { + + /** + * Augment CoreEventsData interface with events specific to this service. + * + * @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation + */ + export interface CoreEventsData { + [AddonModSurveySyncProvider.AUTO_SYNCED]: AddonModSurveyAutoSyncData; + } + +} + +/** + * Data returned by a assign sync. + */ +export type AddonModSurveySyncResult = { + warnings: string[]; // List of warnings. + answersSent: boolean; // Whether some data was sent to the server or offline data was updated. + courseId?: number; // Course the survey belongs to (if known). +}; + +/** + * Data passed to AUTO_SYNCED event. + */ +export type AddonModSurveyAutoSyncData = { + surveyId: number; + warnings: string[]; + userId: number; +}; diff --git a/src/addons/mod/survey/services/survey.ts b/src/addons/mod/survey/services/survey.ts new file mode 100644 index 000000000..a6d5d9997 --- /dev/null +++ b/src/addons/mod/survey/services/survey.ts @@ -0,0 +1,393 @@ +// (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 { Injectable } from '@angular/core'; +import { CoreError } from '@classes/errors/error'; +import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; +import { CoreCourseCommonModWSOptions } from '@features/course/services/course'; +import { CoreCourseLogHelper } from '@features/course/services/log-helper'; +import { CoreApp } from '@services/app'; +import { CoreFilepool } from '@services/filepool'; +import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; +import { makeSingleton } from '@singletons'; +import { AddonModSurveyOffline } from './survey-offline'; + +const ROOT_CACHE_KEY = 'mmaModSurvey:'; + +/** + * Service that provides some features for surveys. + */ +@Injectable( { providedIn: 'root' }) +export class AddonModSurveyProvider { + + static readonly COMPONENT = 'mmaModSurvey'; + + /** + * Get a survey's questions. + * + * @param surveyId Survey ID. + * @param options Other options. + * @return Promise resolved when the questions are retrieved. + */ + async getQuestions(surveyId: number, options: CoreCourseCommonModWSOptions = {}): Promise { + const site = await CoreSites.getSite(options.siteId); + + const params: AddonModSurveyGetQuestionsWSParams = { + surveyid: surveyId, + }; + + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getQuestionsCacheKey(surveyId), + updateFrequency: CoreSite.FREQUENCY_RARELY, + component: AddonModSurveyProvider.COMPONENT, + componentId: options.cmId, + ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. + }; + + const response = await site.read('mod_survey_get_questions', params, preSets); + if (response.questions) { + return response.questions; + } + + throw new CoreError('No questions were found.'); + } + + /** + * Get cache key for survey questions WS calls. + * + * @param surveyId Survey ID. + * @return Cache key. + */ + protected getQuestionsCacheKey(surveyId: number): string { + return ROOT_CACHE_KEY + 'questions:' + surveyId; + } + + /** + * Get cache key for survey data WS calls. + * + * @param courseId Course ID. + * @return Cache key. + */ + protected getSurveyCacheKey(courseId: number): string { + return ROOT_CACHE_KEY + 'survey:' + courseId; + } + + /** + * Get a survey data. + * + * @param courseId Course ID. + * @param key Name of the property to check. + * @param value Value to search. + * @param options Other options. + * @return Promise resolved when the survey is retrieved. + */ + protected async getSurveyDataByKey( + courseId: number, + key: string, + value: number, + options: CoreSitesCommonWSOptions = {}, + ): Promise { + const site = await CoreSites.getSite(options.siteId); + + const params: AddonModSurveyGetSurveysByCoursesWSParams = { + courseids: [courseId], + }; + + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getSurveyCacheKey(courseId), + updateFrequency: CoreSite.FREQUENCY_RARELY, + component: AddonModSurveyProvider.COMPONENT, + ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. + }; + + const response = + await site.read('mod_survey_get_surveys_by_courses', params, preSets); + + const currentSurvey = response.surveys.find((survey) => survey[key] == value); + if (currentSurvey) { + return currentSurvey; + } + + throw new CoreError('Activity not found.'); + } + + /** + * Get a survey by course module ID. + * + * @param courseId Course ID. + * @param cmId Course module ID. + * @param options Other options. + * @return Promise resolved when the survey is retrieved. + */ + getSurvey(courseId: number, cmId: number, options: CoreSitesCommonWSOptions = {}): Promise { + return this.getSurveyDataByKey(courseId, 'coursemodule', cmId, options); + } + + /** + * Get a survey by ID. + * + * @param courseId Course ID. + * @param id Survey ID. + * @param options Other options. + * @return Promise resolved when the survey is retrieved. + */ + getSurveyById(courseId: number, id: number, options: CoreSitesCommonWSOptions = {}): Promise { + return this.getSurveyDataByKey(courseId, 'id', id, options); + } + + /** + * Invalidate the prefetched content. + * + * @param moduleId The module ID. + * @param courseId Course ID of the module. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. + */ + async invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise { + siteId = siteId || CoreSites.getCurrentSiteId(); + + const promises: Promise[] = []; + + promises.push(this.getSurvey(courseId, moduleId).then(async (survey) => { + const ps: Promise[] = []; + + // Do not invalidate activity data before getting activity info, we need it! + ps.push(this.invalidateSurveyData(courseId, siteId)); + ps.push(this.invalidateQuestions(survey.id, siteId)); + + await Promise.all(ps); + + return; + })); + + promises.push(CoreFilepool.invalidateFilesByComponent(siteId, AddonModSurveyProvider.COMPONENT, moduleId)); + + await CoreUtils.allPromises(promises); + } + + /** + * Invalidates survey questions. + * + * @param surveyId Survey ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. + */ + async invalidateQuestions(surveyId: number, siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + await site.invalidateWsCacheForKey(this.getQuestionsCacheKey(surveyId)); + } + + /** + * Invalidates survey data. + * + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. + */ + async invalidateSurveyData(courseId: number, siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + await site.invalidateWsCacheForKey(this.getSurveyCacheKey(courseId)); + } + + /** + * Report the survey as being viewed. + * + * @param id Module ID. + * @param name Name of the assign. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. + */ + async logView(id: number, name?: string, siteId?: string): Promise { + const params: AddonModSurveyViewSurveyWSParams = { + surveyid: id, + }; + + await CoreCourseLogHelper.logSingle( + 'mod_survey_view_survey', + params, + AddonModSurveyProvider.COMPONENT, + id, + name, + 'survey', + {}, + siteId, + ); + } + + /** + * Send survey answers. If cannot send them to Moodle, they'll be stored in offline to be sent later. + * + * @param surveyId Survey ID. + * @param name Survey name. + * @param courseId Course ID the survey belongs to. + * @param answers Answers. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean if success: true if answers were sent to server, + * false if stored in device. + */ + async submitAnswers( + surveyId: number, + name: string, + courseId: number, + answers: AddonModSurveySubmitAnswerData[], + siteId?: string, + ): Promise { + + // Convenience function to store a survey to be synchronized later. + const storeOffline = async (): Promise => { + await AddonModSurveyOffline.saveAnswers(surveyId, name, courseId, answers, siteId); + + return false; + }; + + siteId = siteId || CoreSites.getCurrentSiteId(); + + if (!CoreApp.isOnline()) { + // App is offline, store the message. + return storeOffline(); + } + + try { + // If there's already answers to be sent to the server, discard it first. + await AddonModSurveyOffline.deleteSurveyAnswers(surveyId, siteId); + + // Device is online, try to send them to server. + await this.submitAnswersOnline(surveyId, answers, siteId); + + return true; + } catch (error) { + if (CoreUtils.isWebServiceError(error)) { + // It's a WebService error, the user cannot send the message so don't store it. + throw error; + } + + return storeOffline(); + } + } + + /** + * Send survey answers to Moodle. + * + * @param surveyId Survey ID. + * @param answers Answers. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when answers are successfully submitted. + */ + async submitAnswersOnline(surveyId: number, answers: AddonModSurveySubmitAnswerData[], siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + const params: AddonModSurveySubmitAnswersWSParams = { + surveyid: surveyId, + answers: answers, + }; + + const response = await site.write('mod_survey_submit_answers', params); + if (!response.status) { + throw new CoreError('Error submitting answers.'); + } + } + +} +export const AddonModSurvey = makeSingleton(AddonModSurveyProvider); + +/** + * Params of mod_survey_view_survey WS. + */ +type AddonModSurveyViewSurveyWSParams = { + surveyid: number; // Survey instance id. +}; + +/** + * Survey returned by WS mod_survey_get_surveys_by_courses. + */ +export type AddonModSurveySurvey = { + id: number; // Survey id. + coursemodule: number; // Course module id. + course: number; // Course id. + name: string; // Survey name. + intro?: string; // The Survey intro. + introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + introfiles?: CoreWSExternalFile[]; // @since 3.2. + template?: number; // Survey type. + days?: number; // Days. + questions?: string; // Question ids. + surveydone?: number; // Did I finish the survey?. + timecreated?: number; // Time of creation. + timemodified?: number; // Time of last modification. + section?: number; // Course section id. + visible?: number; // Visible. + groupmode?: number; // Group mode. + groupingid?: number; // Group id. +}; + +/** + * Survey question. + */ +export type AddonModSurveyQuestion = { + id: number; // Question id. + text: string; // Question text. + shorttext: string; // Question short text. + multi: string; // Subquestions ids. + intro: string; // The question intro. + type: number; // Question type. + options: string; // Question options. + parent: number; // Parent question (for subquestions). +}; + +/** + * Params of mod_survey_get_questions WS. + */ +type AddonModSurveyGetQuestionsWSParams = { + surveyid: number; // Survey instance id. +}; + +/** + * Data returned by mod_survey_get_questions WS. + */ +export type AddonModSurveyGetQuestionsWSResponse = { + questions: AddonModSurveyQuestion[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Params of mod_survey_get_surveys_by_courses WS. + */ +type AddonModSurveyGetSurveysByCoursesWSParams = { + courseids?: number[]; // Array of course ids. +}; + +/** + * Data returned by mod_survey_get_surveys_by_courses WS. + */ +export type AddonModSurveyGetSurveysByCoursesWSResponse = { + surveys: AddonModSurveySurvey[]; + warnings?: CoreWSExternalWarning[]; +}; + +export type AddonModSurveySubmitAnswerData = { + key: string; // Answer key. + value: string; // Answer value. +}; + +/** + * Params of mod_survey_submit_answers WS. + */ +type AddonModSurveySubmitAnswersWSParams = { + surveyid: number; // Survey id. + answers: AddonModSurveySubmitAnswerData[]; +}; diff --git a/src/addons/mod/survey/survey-lazy.module.ts b/src/addons/mod/survey/survey-lazy.module.ts new file mode 100644 index 000000000..bd372d315 --- /dev/null +++ b/src/addons/mod/survey/survey-lazy.module.ts @@ -0,0 +1,39 @@ +// (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 { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { AddonModSurveyIndexPage } from './pages/index'; +import { AddonModSurveyComponentsModule } from './components/components.module'; + +const routes: Routes = [ + { + path: ':courseId/:cmId', + component: AddonModSurveyIndexPage, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + CoreSharedModule, + AddonModSurveyComponentsModule, + ], + declarations: [ + AddonModSurveyIndexPage, + ], +}) +export class AddonModSurveyLazyModule {} diff --git a/src/addons/mod/survey/survey.module.ts b/src/addons/mod/survey/survey.module.ts new file mode 100644 index 000000000..0b00f21ae --- /dev/null +++ b/src/addons/mod/survey/survey.module.ts @@ -0,0 +1,75 @@ +// (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 { APP_INITIALIZER, NgModule, Type } from '@angular/core'; +import { Routes } from '@angular/router'; +import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; +import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; +import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; +import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; +import { CoreCronDelegate } from '@services/cron'; +import { CORE_SITE_SCHEMAS } from '@services/sites'; +import { AddonModSurveyComponentsModule } from './components/components.module'; +import { ADDON_MOD_SURVEY_OFFLINE_SITE_SCHEMA } from './services/database/survey'; +import { AddonModSurveyIndexLinkHandler } from './services/handlers/index-link'; +import { AddonModSurveyListLinkHandler } from './services/handlers/list-link'; +import { AddonModSurveyModuleHandler, AddonModSurveyModuleHandlerService } from './services/handlers/module'; +import { AddonModSurveyPrefetchHandler } from './services/handlers/prefetch'; +import { AddonModSurveySyncCronHandler } from './services/handlers/sync-cron'; +import { AddonModSurveyProvider } from './services/survey'; +import { AddonModSurveyHelperProvider } from './services/survey-helper'; +import { AddonModSurveyOfflineProvider } from './services/survey-offline'; +import { AddonModSurveySyncProvider } from './services/survey-sync'; + +// List of providers (without handlers). +export const ADDON_MOD_SURVEY_SERVICES: Type[] = [ + AddonModSurveyProvider, + AddonModSurveyHelperProvider, + AddonModSurveySyncProvider, + AddonModSurveyOfflineProvider, +]; + +const routes: Routes = [ + { + path: AddonModSurveyModuleHandlerService.PAGE_NAME, + loadChildren: () => import('./survey-lazy.module').then(m => m.AddonModSurveyLazyModule), + }, +]; + +@NgModule({ + imports: [ + CoreMainMenuTabRoutingModule.forChild(routes), + AddonModSurveyComponentsModule, + ], + providers: [ + { + provide: CORE_SITE_SCHEMAS, + useValue: [ADDON_MOD_SURVEY_OFFLINE_SITE_SCHEMA], + multi: true, + }, + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreCourseModuleDelegate.registerHandler(AddonModSurveyModuleHandler.instance); + CoreCourseModulePrefetchDelegate.registerHandler(AddonModSurveyPrefetchHandler.instance); + CoreCronDelegate.register(AddonModSurveySyncCronHandler.instance); + CoreContentLinksDelegate.registerHandler(AddonModSurveyIndexLinkHandler.instance); + CoreContentLinksDelegate.registerHandler(AddonModSurveyListLinkHandler.instance); + }, + }, + ], +}) +export class AddonModSurveyModule {} diff --git a/src/core/features/compile/services/compile.ts b/src/core/features/compile/services/compile.ts index e07d3e619..871c943c0 100644 --- a/src/core/features/compile/services/compile.ts +++ b/src/core/features/compile/services/compile.ts @@ -137,7 +137,7 @@ import { ADDON_MOD_PAGE_SERVICES } from '@addons/mod/page/page.module'; import { ADDON_MOD_QUIZ_SERVICES } from '@addons/mod/quiz/quiz.module'; import { ADDON_MOD_RESOURCE_SERVICES } from '@addons/mod/resource/resource.module'; // @todo import { ADDON_MOD_SCORM_SERVICES } from '@addons/mod/scorm/scorm.module'; -// @todo import { ADDON_MOD_SURVEY_SERVICES } from '@addons/mod/survey/survey.module'; +import { ADDON_MOD_SURVEY_SERVICES } from '@addons/mod/survey/survey.module'; import { ADDON_MOD_URL_SERVICES } from '@addons/mod/url/url.module'; // @todo import { ADDON_MOD_WIKI_SERVICES } from '@addons/mod/wiki/wiki.module'; // @todo import { ADDON_MOD_WORKSHOP_SERVICES } from '@addons/mod/workshop/workshop.module'; @@ -302,7 +302,7 @@ export class CoreCompileProvider { ...ADDON_MOD_QUIZ_SERVICES, ...ADDON_MOD_RESOURCE_SERVICES, // @todo ...ADDON_MOD_SCORM_SERVICES, - // @todo ...ADDON_MOD_SURVEY_SERVICES, + ...ADDON_MOD_SURVEY_SERVICES, ...ADDON_MOD_URL_SERVICES, // @todo ...ADDON_MOD_WIKI_SERVICES, // @todo ...ADDON_MOD_WORKSHOP_SERVICES, From 3dbe1d12d797dd54a5d4c74cb1baeb5683c7aec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 18 Mar 2021 11:19:36 +0100 Subject: [PATCH 2/4] MOBILE-3654 forum: Rename service files --- src/addons/mod/forum/components/edit-post/edit-post.ts | 2 +- src/addons/mod/forum/components/index/index.ts | 6 +++--- src/addons/mod/forum/components/post/post.ts | 6 +++--- src/addons/mod/forum/forum.module.ts | 6 +++--- src/addons/mod/forum/pages/discussion/discussion.page.ts | 6 +++--- .../mod/forum/pages/new-discussion/new-discussion.page.ts | 6 +++--- src/addons/mod/forum/services/database/offline.ts | 2 +- .../mod/forum/services/{helper.ts => forum-helper.ts} | 2 +- .../mod/forum/services/{offline.ts => forum-offline.ts} | 0 src/addons/mod/forum/services/{sync.ts => forum-sync.ts} | 4 ++-- src/addons/mod/forum/services/forum.ts | 2 +- src/addons/mod/forum/services/handlers/prefetch.ts | 2 +- src/addons/mod/forum/services/handlers/sync-cron.ts | 2 +- 13 files changed, 23 insertions(+), 23 deletions(-) rename src/addons/mod/forum/services/{helper.ts => forum-helper.ts} (99%) rename src/addons/mod/forum/services/{offline.ts => forum-offline.ts} (100%) rename src/addons/mod/forum/services/{sync.ts => forum-sync.ts} (99%) diff --git a/src/addons/mod/forum/components/edit-post/edit-post.ts b/src/addons/mod/forum/components/edit-post/edit-post.ts index 230333110..958db0662 100644 --- a/src/addons/mod/forum/components/edit-post/edit-post.ts +++ b/src/addons/mod/forum/components/edit-post/edit-post.ts @@ -19,7 +19,7 @@ import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { ModalController, Translate } from '@singletons'; import { AddonModForumData, AddonModForumPost, AddonModForumReply } from '@addons/mod/forum/services/forum'; -import { AddonModForumHelper } from '@addons/mod/forum/services/helper'; +import { AddonModForumHelper } from '@addons/mod/forum/services/forum-helper'; /** * Page that displays a form to edit discussion post. diff --git a/src/addons/mod/forum/components/index/index.ts b/src/addons/mod/forum/components/index/index.ts index 5bd493c78..77763956e 100644 --- a/src/addons/mod/forum/components/index/index.ts +++ b/src/addons/mod/forum/components/index/index.ts @@ -25,10 +25,10 @@ import { AddonModForumNewDiscussionData, AddonModForumReplyDiscussionData, } from '@addons/mod/forum/services/forum'; -import { AddonModForumOffline, AddonModForumOfflineDiscussion } from '@addons/mod/forum/services/offline'; +import { AddonModForumOffline, AddonModForumOfflineDiscussion } from '@addons/mod/forum/services/forum-offline'; import { ModalController, PopoverController, Translate } from '@singletons'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; -import { AddonModForumHelper } from '@addons/mod/forum/services/helper'; +import { AddonModForumHelper } from '@addons/mod/forum/services/forum-helper'; import { CoreGroups, CoreGroupsProvider } from '@services/groups'; import { CoreEvents, CoreEventObserver } from '@singletons/events'; import { @@ -36,7 +36,7 @@ import { AddonModForumManualSyncData, AddonModForumSyncProvider, AddonModForumSyncResult, -} from '@addons/mod/forum/services/sync'; +} from '@addons/mod/forum/services/forum-sync'; import { CoreSites } from '@services/sites'; import { CoreUser } from '@features/user/services/user'; import { CoreDomUtils } from '@services/utils/dom'; diff --git a/src/addons/mod/forum/components/post/post.ts b/src/addons/mod/forum/components/post/post.ts index b87811f9c..3e244193b 100644 --- a/src/addons/mod/forum/components/post/post.ts +++ b/src/addons/mod/forum/components/post/post.ts @@ -44,11 +44,11 @@ import { CoreTag } from '@features/tag/services/tag'; import { ModalController, PopoverController, Translate } from '@singletons'; import { CoreFileEntry, CoreFileUploader } from '@features/fileuploader/services/fileuploader'; import { IonContent } from '@ionic/angular'; -import { AddonModForumSync } from '../../services/sync'; +import { AddonModForumSync } from '../../services/forum-sync'; import { CoreSync } from '@services/sync'; import { CoreTextUtils } from '@services/utils/text'; -import { AddonModForumHelper } from '../../services/helper'; -import { AddonModForumOffline, AddonModForumReplyOptions } from '../../services/offline'; +import { AddonModForumHelper } from '../../services/forum-helper'; +import { AddonModForumOffline, AddonModForumReplyOptions } from '../../services/forum-offline'; import { CoreUtils } from '@services/utils/utils'; import { AddonModForumPostOptionsMenuComponent } from '../post-options-menu/post-options-menu'; import { AddonModForumEditPostComponent } from '../edit-post/edit-post'; diff --git a/src/addons/mod/forum/forum.module.ts b/src/addons/mod/forum/forum.module.ts index 62acfa715..0079e58bb 100644 --- a/src/addons/mod/forum/forum.module.ts +++ b/src/addons/mod/forum/forum.module.ts @@ -39,9 +39,9 @@ import { AddonModForumTagAreaHandler } from './services/handlers/tag-area'; import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate'; import { AddonModForumPushClickHandler } from './services/handlers/push-click'; import { AddonModForumProvider } from './services/forum'; -import { AddonModForumOfflineProvider } from './services/offline'; -import { AddonModForumHelperProvider } from './services/helper'; -import { AddonModForumSyncProvider } from './services/sync'; +import { AddonModForumOfflineProvider } from './services/forum-offline'; +import { AddonModForumHelperProvider } from './services/forum-helper'; +import { AddonModForumSyncProvider } from './services/forum-sync'; export const ADDON_MOD_FORUM_SERVICES: Type[] = [ AddonModForumProvider, diff --git a/src/addons/mod/forum/pages/discussion/discussion.page.ts b/src/addons/mod/forum/pages/discussion/discussion.page.ts index 0b154e89c..1f04eef18 100644 --- a/src/addons/mod/forum/pages/discussion/discussion.page.ts +++ b/src/addons/mod/forum/pages/discussion/discussion.page.ts @@ -39,9 +39,9 @@ import { AddonModForumPost, AddonModForumProvider, } from '../../services/forum'; -import { AddonModForumHelper } from '../../services/helper'; -import { AddonModForumOffline } from '../../services/offline'; -import { AddonModForumSync, AddonModForumSyncProvider } from '../../services/sync'; +import { AddonModForumHelper } from '../../services/forum-helper'; +import { AddonModForumOffline } from '../../services/forum-offline'; +import { AddonModForumSync, AddonModForumSyncProvider } from '../../services/forum-sync'; type SortType = 'flat-newest' | 'flat-oldest' | 'nested'; diff --git a/src/addons/mod/forum/pages/new-discussion/new-discussion.page.ts b/src/addons/mod/forum/pages/new-discussion/new-discussion.page.ts index 5fa1376bb..8ff69c794 100644 --- a/src/addons/mod/forum/pages/new-discussion/new-discussion.page.ts +++ b/src/addons/mod/forum/pages/new-discussion/new-discussion.page.ts @@ -26,14 +26,14 @@ import { AddonModForumProvider, } from '@addons/mod/forum/services/forum'; import { CoreEditorRichTextEditorComponent } from '@features/editor/components/rich-text-editor/rich-text-editor'; -import { AddonModForumSync, AddonModForumSyncProvider } from '@addons/mod/forum/services/sync'; +import { AddonModForumSync, AddonModForumSyncProvider } from '@addons/mod/forum/services/forum-sync'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { Translate } from '@singletons'; import { CoreSync } from '@services/sync'; -import { AddonModForumDiscussionOptions, AddonModForumOffline } from '@addons/mod/forum/services/offline'; +import { AddonModForumDiscussionOptions, AddonModForumOffline } from '@addons/mod/forum/services/forum-offline'; import { CoreUtils } from '@services/utils/utils'; -import { AddonModForumHelper } from '@addons/mod/forum/services/helper'; +import { AddonModForumHelper } from '@addons/mod/forum/services/forum-helper'; import { IonRefresher } from '@ionic/angular'; import { CoreFileUploader } from '@features/fileuploader/services/fileuploader'; import { CoreTextUtils } from '@services/utils/text'; diff --git a/src/addons/mod/forum/services/database/offline.ts b/src/addons/mod/forum/services/database/offline.ts index ce28a9776..7feee5936 100644 --- a/src/addons/mod/forum/services/database/offline.ts +++ b/src/addons/mod/forum/services/database/offline.ts @@ -13,7 +13,7 @@ // limitations under the License. import { CoreSiteSchema } from '@services/sites'; -import { AddonModForumOfflineDiscussion, AddonModForumOfflineReply } from '../offline'; +import { AddonModForumOfflineDiscussion, AddonModForumOfflineReply } from '../forum-offline'; /** * Database variables for AddonModForum service. diff --git a/src/addons/mod/forum/services/helper.ts b/src/addons/mod/forum/services/forum-helper.ts similarity index 99% rename from src/addons/mod/forum/services/helper.ts rename to src/addons/mod/forum/services/forum-helper.ts index f80b1f2d6..c479f1045 100644 --- a/src/addons/mod/forum/services/helper.ts +++ b/src/addons/mod/forum/services/forum-helper.ts @@ -30,7 +30,7 @@ import { AddonModForumPost, AddonModForumProvider, } from './forum'; -import { AddonModForumDiscussionOptions, AddonModForumOffline, AddonModForumOfflineReply } from './offline'; +import { AddonModForumDiscussionOptions, AddonModForumOffline, AddonModForumOfflineReply } from './forum-offline'; /** * Service that provides some features for forums. diff --git a/src/addons/mod/forum/services/offline.ts b/src/addons/mod/forum/services/forum-offline.ts similarity index 100% rename from src/addons/mod/forum/services/offline.ts rename to src/addons/mod/forum/services/forum-offline.ts diff --git a/src/addons/mod/forum/services/sync.ts b/src/addons/mod/forum/services/forum-sync.ts similarity index 99% rename from src/addons/mod/forum/services/sync.ts rename to src/addons/mod/forum/services/forum-sync.ts index 1e2e518a3..4d5eb3c08 100644 --- a/src/addons/mod/forum/services/sync.ts +++ b/src/addons/mod/forum/services/forum-sync.ts @@ -34,8 +34,8 @@ import { AddonModForumAddDiscussionWSOptionsObject, AddonModForumProvider, } from './forum'; -import { AddonModForumHelper } from './helper'; -import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumOfflineReply } from './offline'; +import { AddonModForumHelper } from './forum-helper'; +import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumOfflineReply } from './forum-offline'; declare module '@singletons/events' { diff --git a/src/addons/mod/forum/services/forum.ts b/src/addons/mod/forum/services/forum.ts index 60c8fcdc5..66dbf417c 100644 --- a/src/addons/mod/forum/services/forum.ts +++ b/src/addons/mod/forum/services/forum.ts @@ -27,7 +27,7 @@ import { CoreUrlUtils } from '@services/utils/url'; import { CoreUtils } from '@services/utils/utils'; import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; import { makeSingleton, Translate } from '@singletons'; -import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumReplyOptions } from './offline'; +import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumReplyOptions } from './forum-offline'; const ROOT_CACHE_KEY = 'mmaModForum:'; diff --git a/src/addons/mod/forum/services/handlers/prefetch.ts b/src/addons/mod/forum/services/handlers/prefetch.ts index 0688a5747..c303ec8a0 100644 --- a/src/addons/mod/forum/services/handlers/prefetch.ts +++ b/src/addons/mod/forum/services/handlers/prefetch.ts @@ -22,7 +22,7 @@ import { CoreCourse, CoreCourseAnyModuleData, CoreCourseCommonModWSOptions } fro import { CoreUser } from '@features/user/services/user'; import { CoreGroups, CoreGroupsProvider } from '@services/groups'; import { CoreUtils } from '@services/utils/utils'; -import { AddonModForumSync } from '../sync'; +import { AddonModForumSync } from '../forum-sync'; import { makeSingleton } from '@singletons'; /** diff --git a/src/addons/mod/forum/services/handlers/sync-cron.ts b/src/addons/mod/forum/services/handlers/sync-cron.ts index 2544f3bd1..1cb25bb45 100644 --- a/src/addons/mod/forum/services/handlers/sync-cron.ts +++ b/src/addons/mod/forum/services/handlers/sync-cron.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreCronHandler } from '@services/cron'; import { makeSingleton } from '@singletons'; -import { AddonModForumSync } from '../sync'; +import { AddonModForumSync } from '../forum-sync'; /** * Synchronization cron handler. From 28bf4243bc3dba1860b938438f9cf89a4c3bc413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 18 Mar 2021 11:19:06 +0100 Subject: [PATCH 3/4] MOBILE-3654 sync: Add component translatable string --- src/addons/calendar/services/calendar-sync.ts | 17 +++------ src/addons/mod/assign/services/assign-sync.ts | 21 ++--------- src/addons/mod/forum/services/forum-sync.ts | 35 ++++-------------- .../h5pactivity/services/h5pactivity-sync.ts | 26 ++----------- src/addons/mod/lesson/services/lesson-sync.ts | 37 +++++++------------ src/addons/mod/quiz/services/quiz-sync.ts | 3 +- src/addons/mod/survey/services/survey-sync.ts | 12 ++---- src/core/classes/base-sync.ts | 32 +++++++++++++--- src/core/classes/errors/error.ts | 4 ++ .../features/course/classes/activity-sync.ts | 15 +++++++- src/core/services/utils/text.ts | 4 +- 11 files changed, 85 insertions(+), 121 deletions(-) diff --git a/src/addons/calendar/services/calendar-sync.ts b/src/addons/calendar/services/calendar-sync.ts index e0e44e6f6..c167865d3 100644 --- a/src/addons/calendar/services/calendar-sync.ts +++ b/src/addons/calendar/services/calendar-sync.ts @@ -28,7 +28,6 @@ import { AddonCalendarOffline } from './calendar-offline'; import { AddonCalendarHelper } from './calendar-helper'; import { makeSingleton, Translate } from '@singletons'; import { CoreSync } from '@services/sync'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreNetworkError } from '@classes/errors/network-error'; /** @@ -41,6 +40,8 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider { siteId = siteId || CoreSites.getCurrentSiteId(); - this.componentTranslate = this.componentTranslate || CoreCourse.translateModuleName('assign'); if (this.isSyncing(assignId, siteId)) { // There's already a sync ongoing for this assign, return the promise. @@ -328,7 +325,6 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid // The submission was modified in Moodle, discard the submission. this.addOfflineDataDeletedWarning( warnings, - this.componentTranslate, assign.name, Translate.instant('addon.mod_assign.warningsubmissionmodified'), ); @@ -369,12 +365,7 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid } // A WebService has thrown an error, this means it cannot be submitted. Discard the submission. - this.addOfflineDataDeletedWarning( - warnings, - this.componentTranslate, - assign.name, - CoreTextUtils.getErrorMessageFromError(error) || '', - ); + this.addOfflineDataDeletedWarning(warnings, assign.name, error); } // Delete the offline data. @@ -458,7 +449,6 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid // The submission grade was modified in Moodle, discard it. this.addOfflineDataDeletedWarning( warnings, - this.componentTranslate, assign.name, Translate.instant('addon.mod_assign.warningsubmissiongrademodified'), ); @@ -527,12 +517,7 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid } // A WebService has thrown an error, this means it cannot be submitted. Discard the submission. - this.addOfflineDataDeletedWarning( - warnings, - this.componentTranslate, - assign.name, - CoreTextUtils.getErrorMessageFromError(error) || '', - ); + this.addOfflineDataDeletedWarning(warnings, assign.name, error); } // Delete the offline data. diff --git a/src/addons/mod/forum/services/forum-sync.ts b/src/addons/mod/forum/services/forum-sync.ts index 4d5eb3c08..479fe33a7 100644 --- a/src/addons/mod/forum/services/forum-sync.ts +++ b/src/addons/mod/forum/services/forum-sync.ts @@ -14,8 +14,7 @@ import { ContextLevel } from '@/core/constants'; import { Injectable } from '@angular/core'; -import { CoreSyncBaseProvider } from '@classes/base-sync'; -import { CoreCourse } from '@features/course/services/course'; +import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync'; import { CoreCourseLogHelper } from '@features/course/services/log-helper'; import { CoreFileUploader } from '@features/fileuploader/services/fileuploader'; import { CoreRatingSync } from '@features/rating/services/rating-sync'; @@ -23,7 +22,6 @@ import { CoreApp } from '@services/app'; import { CoreGroups } from '@services/groups'; import { CoreSites } from '@services/sites'; import { CoreSync } from '@services/sync'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton, Translate } from '@singletons'; import { CoreArray } from '@singletons/array'; @@ -55,25 +53,17 @@ declare module '@singletons/events' { * Service to sync forums. */ @Injectable({ providedIn: 'root' }) -export class AddonModForumSyncProvider extends CoreSyncBaseProvider { +export class AddonModForumSyncProvider extends CoreCourseActivitySyncBaseProvider { static readonly AUTO_SYNCED = 'addon_mod_forum_autom_synced'; static readonly MANUAL_SYNCED = 'addon_mod_forum_manual_synced'; - private _componentTranslate?: string; + protected componentTranslatableString = 'forum'; constructor() { super('AddonModForumSyncProvider'); } - protected get componentTranslate(): string { - if (!this._componentTranslate) { - this._componentTranslate = CoreCourse.translateModuleName('forum'); - } - - return this._componentTranslate; - } - /** * Try to synchronize all the forums in a certain site or in all sites. * @@ -291,11 +281,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { result.warnings.forEach((warning) => { - warnings.push(Translate.instant('core.warningofflinedatadeleted', { - component: this.componentTranslate, - name: forum.name, - error: warning, - })); + this.addOfflineDataDeletedWarning(warnings, forum.name, warning); }); return; @@ -512,11 +494,8 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { siteId = siteId || CoreSites.getCurrentSiteId(); - this.componentTranslate = this.componentTranslate || CoreCourse.translateModuleName('lesson'); let syncPromise = this.getOngoingSync(lessonId, siteId); if (syncPromise) { @@ -317,11 +315,12 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid if (attempts.length != attemptsLength) { // Some attempts won't be sent, add a warning. - result.warnings.push(Translate.instant('core.warningofflinedatadeleted', { - component: this.componentTranslate, - name: lesson.name, - error: Translate.instant('addon.mod_lesson.warningretakefinished'), - })); + this.addOfflineDataDeletedWarning( + result.warnings, + lesson.name, + Translate.instant('addon.mod_lesson.warningretakefinished'), + ); + } await Promise.all(promises); @@ -386,11 +385,7 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid await AddonModLessonOffline.deleteAttempt(lesson.id, retake, pageId, timemodified, siteId); // Attempt deleted, add a warning. - result.warnings.push(Translate.instant('core.warningofflinedatadeleted', { - component: this.componentTranslate, - name: lesson.name, - error: CoreTextUtils.getErrorMessageFromError(error), - })); + this.addOfflineDataDeletedWarning(result.warnings, lesson.name, error); } } @@ -447,11 +442,11 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid if (retake.retake != passwordData.accessInfo.attemptscount) { // The retake changed, add a warning if it isn't there already. if (!result.warnings.length) { - result.warnings.push(Translate.instant('core.warningofflinedatadeleted', { - component: this.componentTranslate, - name: passwordData.lesson.name, - error: Translate.instant('addon.mod_lesson.warningretakefinished'), - })); + this.addOfflineDataDeletedWarning( + result.warnings, + passwordData.lesson.name, + Translate.instant('addon.mod_lesson.warningretakefinished'), + ); } await AddonModLessonOffline.deleteRetake(lessonId, siteId); @@ -488,11 +483,7 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid await AddonModLessonOffline.deleteRetake(lessonId, siteId); // Retake deleted, add a warning. - result.warnings.push(Translate.instant('core.warningofflinedatadeleted', { - component: this.componentTranslate, - name: passwordData.lesson.name, - error: CoreTextUtils.getErrorMessageFromError(error), - })); + this.addOfflineDataDeletedWarning(result.warnings, passwordData.lesson.name, error); } } diff --git a/src/addons/mod/quiz/services/quiz-sync.ts b/src/addons/mod/quiz/services/quiz-sync.ts index a7f1bb273..77ce8beff 100644 --- a/src/addons/mod/quiz/services/quiz-sync.ts +++ b/src/addons/mod/quiz/services/quiz-sync.ts @@ -40,7 +40,7 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider static readonly AUTO_SYNCED = 'addon_mod_quiz_autom_synced'; - protected componentTranslate?: string; + protected componentTranslatableString = 'quiz'; constructor() { super('AddonModQuizSyncProvider'); @@ -271,7 +271,6 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider // Verify that quiz isn't blocked. if (CoreSync.isBlocked(AddonModQuizProvider.COMPONENT, quiz.id, siteId)) { this.logger.debug('Cannot sync quiz ' + quiz.id + ' because it is blocked.'); - this.componentTranslate = this.componentTranslate || CoreCourse.translateModuleName('quiz'); throw new CoreError(Translate.instant('core.errorsyncblocked', { $a: this.componentTranslate })); } diff --git a/src/addons/mod/survey/services/survey-sync.ts b/src/addons/mod/survey/services/survey-sync.ts index c2c10cb39..f9c70bf91 100644 --- a/src/addons/mod/survey/services/survey-sync.ts +++ b/src/addons/mod/survey/services/survey-sync.ts @@ -19,9 +19,8 @@ import { CoreCourse } from '@features/course/services/course'; import { CoreCourseLogHelper } from '@features/course/services/log-helper'; import { CoreApp } from '@services/app'; import { CoreSites } from '@services/sites'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; -import { makeSingleton, Translate } from '@singletons'; +import { makeSingleton } from '@singletons'; import { CoreEvents } from '@singletons/events'; import { AddonModSurveyPrefetchHandler } from './handlers/prefetch'; import { AddonModSurvey, AddonModSurveyProvider } from './survey'; @@ -35,11 +34,10 @@ export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvid static readonly AUTO_SYNCED = 'addon_mod_survey_autom_synced'; - protected componentTranslate: string; + protected componentTranslatableString = 'survey'; constructor() { super('AddonModSurveySyncProvider'); - this.componentTranslate = CoreCourse.translateModuleName('survey'); } /** @@ -196,11 +194,7 @@ export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvid await AddonModSurveyOffline.deleteSurveyAnswers(surveyId, siteId, userId); // Answers deleted, add a warning. - result.warnings.push(Translate.instant('core.warningofflinedatadeleted', { - component: this.componentTranslate, - name: data.name, - error: CoreTextUtils.getErrorMessageFromError(error), - })); + this.addOfflineDataDeletedWarning(result.warnings, data.name, error); } if (result.courseId) { diff --git a/src/core/classes/base-sync.ts b/src/core/classes/base-sync.ts index 7146f1504..c34516719 100644 --- a/src/core/classes/base-sync.ts +++ b/src/core/classes/base-sync.ts @@ -19,7 +19,7 @@ import { CoreTextUtils } from '@services/utils/text'; import { CoreTimeUtils } from '@services/utils/time'; import { Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; -import { CoreError } from '@classes/errors/error'; +import { CoreAnyError, CoreError } from '@classes/errors/error'; /** * Blocked sync error. @@ -41,6 +41,16 @@ export class CoreSyncBaseProvider { */ component = 'core'; + /** + * Translatable component name string. + */ + protected componentTranslatableString = 'generic component'; + + /** + * Translated name of the component. + */ + protected componentTranslateInternal?: string; + /** * Sync provider's interval. */ @@ -58,15 +68,14 @@ export class CoreSyncBaseProvider { * Add an offline data deleted warning to a list of warnings. * * @param warnings List of warnings. - * @param component Component. * @param name Instance name. * @param error Specific error message. */ - protected addOfflineDataDeletedWarning(warnings: string[], component: string, name: string, error: string): void { + protected addOfflineDataDeletedWarning(warnings: string[], name: string, error: CoreAnyError): void { const warning = Translate.instant('core.warningofflinedatadeleted', { - component: component, + component: this.componentTranslate, name: name, - error: error, + error: CoreTextUtils.getErrorMessageFromError(error), }); if (warnings.indexOf(warning) == -1) { @@ -304,4 +313,17 @@ export class CoreSyncBaseProvider { } } + /** + * Get component name translated. + * + * @return Component name translated. + */ + protected get componentTranslate(): string { + if (!this.componentTranslateInternal) { + this.componentTranslateInternal = Translate.instant(this.componentTranslatableString); + } + + return this.componentTranslateInternal!; + } + } diff --git a/src/core/classes/errors/error.ts b/src/core/classes/errors/error.ts index 2bbb0b737..e046ea3d1 100644 --- a/src/core/classes/errors/error.ts +++ b/src/core/classes/errors/error.ts @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { CoreTextErrorObject } from '@services/utils/text'; + /** * Base Error class. * @@ -31,3 +33,5 @@ export class CoreError extends Error { } } + +export type CoreAnyError = string | CoreError | CoreTextErrorObject | null | undefined; diff --git a/src/core/features/course/classes/activity-sync.ts b/src/core/features/course/classes/activity-sync.ts index 0bfaf2254..8cebdddd6 100644 --- a/src/core/features/course/classes/activity-sync.ts +++ b/src/core/features/course/classes/activity-sync.ts @@ -13,7 +13,7 @@ // limitations under the License. import { CoreSyncBaseProvider } from '@classes/base-sync'; -import { CoreCourseAnyModuleData } from '../services/course'; +import { CoreCourse, CoreCourseAnyModuleData } from '../services/course'; import { CoreCourseModulePrefetchDelegate } from '../services/module-prefetch-delegate'; import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler'; @@ -22,6 +22,8 @@ import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler'; */ export class CoreCourseActivitySyncBaseProvider extends CoreSyncBaseProvider { + protected componentTranslatableString = 'activity'; + /** * Conveniece function to prefetch data after an update. * @@ -54,4 +56,15 @@ export class CoreCourseActivitySyncBaseProvider extends CoreSyncBasePr } } + /** + * @inheritdoc + */ + protected get componentTranslate(): string { + if (!this.componentTranslateInternal) { + this.componentTranslateInternal = CoreCourse.translateModuleName(this.componentTranslatableString); + } + + return this.componentTranslateInternal; + } + } diff --git a/src/core/services/utils/text.ts b/src/core/services/utils/text.ts index 462c28f69..34682af36 100644 --- a/src/core/services/utils/text.ts +++ b/src/core/services/utils/text.ts @@ -18,7 +18,7 @@ import { ModalOptions } from '@ionic/core'; import { CoreApp } from '@services/app'; import { CoreLang } from '@services/lang'; -import { CoreError } from '@classes/errors/error'; +import { CoreAnyError, CoreError } from '@classes/errors/error'; import { makeSingleton, ModalController, Translate } from '@singletons'; import { CoreWSExternalFile } from '@services/ws'; import { Locutus } from '@singletons/locutus'; @@ -533,7 +533,7 @@ export class CoreTextUtilsProvider { * @param error Error. * @return Error message, undefined if not found. */ - getErrorMessageFromError(error?: string | CoreError | CoreTextErrorObject | null): string | undefined { + getErrorMessageFromError(error?: CoreAnyError): string | undefined { if (typeof error == 'string') { return error; } From 0396d5670d50201b940b1bf81d121ea2feda5ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 18 Mar 2021 14:04:16 +0100 Subject: [PATCH 4/4] MOBILE-3654 travis: Add iOS builds --- .travis.yml | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c4332569..499152424 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ os: linux dist: trusty -language: android node_js: 12 if: env(DEPLOY) = 1 @@ -8,16 +7,6 @@ if: env(DEPLOY) = 1 git: depth: 3 -android: - components: - - tools - - platform-tools - - build-tools-29.0.3 - - android-28 - - extra-google-google_play_services - - extra-google-m2repository - - extra-android-m2repository - before_cache: - rm -rf $HOME/.cache/electron-builder/wine - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock @@ -46,3 +35,24 @@ before_script: script: - scripts/build.sh + +jobs: + include: + - stage: build + name: "Build Android" + language: android + android: + components: + - tools + - platform-tools + - build-tools-29.0.3 + - android-28 + - extra-google-google_play_services + - extra-google-m2repository + - extra-android-m2repository + - stage: build + name: "Build iOS" + language: node_js + if: env(DEPLOY) = 1 AND env(BUILD_IOS) = 1 + os: osx + osx_image: xcode12.4