forked from EVOgeek/Vmeda.Online
MOBILE-2061 course: Save manual completion in offline
parent
9b833ab46d
commit
be209e9cb5
|
@ -14,10 +14,10 @@
|
||||||
|
|
||||||
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
import { CoreUserProvider } from '@core/user/providers/user';
|
import { CoreUserProvider } from '@core/user/providers/user';
|
||||||
|
import { CoreCourseProvider } from '../../providers/course';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to handle activity completion. It shows a checkbox with the current status, and allows manually changing
|
* Component to handle activity completion. It shows a checkbox with the current status, and allows manually changing
|
||||||
|
@ -41,7 +41,8 @@ export class CoreCourseModuleCompletionComponent implements OnChanges {
|
||||||
completionDescription: string;
|
completionDescription: string;
|
||||||
|
|
||||||
constructor(private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider,
|
constructor(private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider,
|
||||||
private translate: TranslateService, private sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider) {
|
private translate: TranslateService, private courseProvider: CoreCourseProvider,
|
||||||
|
private userProvider: CoreUserProvider) {
|
||||||
this.completionChanged = new EventEmitter();
|
this.completionChanged = new EventEmitter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,14 +69,11 @@ export class CoreCourseModuleCompletionComponent implements OnChanges {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const modal = this.domUtils.showModalLoading(),
|
const modal = this.domUtils.showModalLoading();
|
||||||
params = {
|
|
||||||
cmid: this.completion.cmid,
|
this.courseProvider.markCompletedManually(this.completion.cmid, this.completion.state === 1 ? 0 : 1,
|
||||||
completed: this.completion.state === 1 ? 0 : 1
|
this.completion.courseId).then((response) => {
|
||||||
},
|
|
||||||
currentSite = this.sitesProvider.getCurrentSite();
|
|
||||||
|
|
||||||
currentSite.write('core_completion_update_activity_completion_status_manually', params).then((response) => {
|
|
||||||
if (!response.status) {
|
if (!response.status) {
|
||||||
return Promise.reject(null);
|
return Promise.reject(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,13 +30,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="module.visible === 0 || module.availabilityinfo || module.handlerData.extraBadge || module.isStealth">
|
<div *ngIf="module.visible === 0 || module.availabilityinfo || module.handlerData.extraBadge || module.isStealth || (module.completionstatus && module.completionstatus.offline)">
|
||||||
<ion-badge item-end *ngIf="module.handlerData.extraBadge" [color]="module.handlerData.extraBadgeColor" text-wrap text-start>
|
<ion-badge item-end *ngIf="module.handlerData.extraBadge" [color]="module.handlerData.extraBadgeColor" text-wrap text-start>
|
||||||
<core-format-text [text]="module.handlerData.extraBadge"></core-format-text>
|
<core-format-text [text]="module.handlerData.extraBadge"></core-format-text>
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
<ion-badge item-end *ngIf="module.visible === 0">{{ 'core.course.hiddenfromstudents' | translate }}</ion-badge>
|
<ion-badge item-end *ngIf="module.visible === 0" text-wrap>{{ 'core.course.hiddenfromstudents' | translate }}</ion-badge>
|
||||||
<ion-badge item-end *ngIf="module.visible !== 0 && module.isStealth">{{ 'core.course.hiddenoncoursepage' | translate }}</ion-badge>
|
<ion-badge item-end *ngIf="module.visible !== 0 && module.isStealth" text-wrap>{{ 'core.course.hiddenoncoursepage' | translate }}</ion-badge>
|
||||||
<ion-badge item-end *ngIf="module.availabilityinfo"><core-format-text [text]="module.availabilityinfo"></core-format-text></ion-badge>
|
<ion-badge item-end *ngIf="module.availabilityinfo" text-wrap><core-format-text [text]="module.availabilityinfo"></core-format-text></ion-badge>
|
||||||
|
<ion-badge item-end *ngIf="module.completionstatus && module.completionstatus.offline" color="warning" text-wrap>{{ 'core.course.manualcompletionnotsynced' | translate }}</ion-badge>
|
||||||
</div>
|
</div>
|
||||||
<core-format-text class="core-module-description" *ngIf="module.description" maxHeight="80" [text]="module.description"></core-format-text>
|
<core-format-text class="core-module-description" *ngIf="module.description" maxHeight="80" [text]="module.description"></core-format-text>
|
||||||
</a>
|
</a>
|
|
@ -17,6 +17,7 @@ import { CoreCourseProvider } from './providers/course';
|
||||||
import { CoreCourseHelperProvider } from './providers/helper';
|
import { CoreCourseHelperProvider } from './providers/helper';
|
||||||
import { CoreCourseFormatDelegate } from './providers/format-delegate';
|
import { CoreCourseFormatDelegate } from './providers/format-delegate';
|
||||||
import { CoreCourseModuleDelegate } from './providers/module-delegate';
|
import { CoreCourseModuleDelegate } from './providers/module-delegate';
|
||||||
|
import { CoreCourseOfflineProvider } from './providers/course-offline';
|
||||||
import { CoreCourseModulePrefetchDelegate } from './providers/module-prefetch-delegate';
|
import { CoreCourseModulePrefetchDelegate } from './providers/module-prefetch-delegate';
|
||||||
import { CoreCourseOptionsDelegate } from './providers/options-delegate';
|
import { CoreCourseOptionsDelegate } from './providers/options-delegate';
|
||||||
import { CoreCourseFormatDefaultHandler } from './providers/default-format';
|
import { CoreCourseFormatDefaultHandler } from './providers/default-format';
|
||||||
|
@ -33,7 +34,8 @@ export const CORE_COURSE_PROVIDERS: any[] = [
|
||||||
CoreCourseFormatDelegate,
|
CoreCourseFormatDelegate,
|
||||||
CoreCourseModuleDelegate,
|
CoreCourseModuleDelegate,
|
||||||
CoreCourseModulePrefetchDelegate,
|
CoreCourseModulePrefetchDelegate,
|
||||||
CoreCourseOptionsDelegate
|
CoreCourseOptionsDelegate,
|
||||||
|
CoreCourseOfflineProvider
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -51,6 +53,7 @@ export const CORE_COURSE_PROVIDERS: any[] = [
|
||||||
CoreCourseModuleDelegate,
|
CoreCourseModuleDelegate,
|
||||||
CoreCourseModulePrefetchDelegate,
|
CoreCourseModulePrefetchDelegate,
|
||||||
CoreCourseOptionsDelegate,
|
CoreCourseOptionsDelegate,
|
||||||
|
CoreCourseOfflineProvider,
|
||||||
CoreCourseFormatDefaultHandler,
|
CoreCourseFormatDefaultHandler,
|
||||||
CoreCourseModuleDefaultHandler
|
CoreCourseModuleDefaultHandler
|
||||||
],
|
],
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"errorgetmodule": "Error getting activity data.",
|
"errorgetmodule": "Error getting activity data.",
|
||||||
"hiddenfromstudents": "Hidden from students",
|
"hiddenfromstudents": "Hidden from students",
|
||||||
"hiddenoncoursepage": "Available but not shown on course page",
|
"hiddenoncoursepage": "Available but not shown on course page",
|
||||||
|
"manualcompletionnotsynced": "Manual completion not synchronised.",
|
||||||
"nocontentavailable": "No content available at the moment.",
|
"nocontentavailable": "No content available at the moment.",
|
||||||
"overriddennotice": "Your final grade from this activity was manually adjusted.",
|
"overriddennotice": "Your final grade from this activity was manually adjusted.",
|
||||||
"refreshcourse": "Refresh course",
|
"refreshcourse": "Refresh course",
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to handle offline data for courses.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreCourseOfflineProvider {
|
||||||
|
|
||||||
|
// Variables for database.
|
||||||
|
static MANUAL_COMPLETION_TABLE = 'course_manual_completion';
|
||||||
|
protected tablesSchema = [
|
||||||
|
{
|
||||||
|
name: CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'cmid',
|
||||||
|
type: 'INTEGER',
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'completed',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'courseid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timecreated',
|
||||||
|
type: 'INTEGER'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(private sitesProvider: CoreSitesProvider) {
|
||||||
|
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a manual completion stored.
|
||||||
|
*
|
||||||
|
* @param {number} cmId The module ID to remove the completion.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when deleted, rejected if failure.
|
||||||
|
*/
|
||||||
|
deleteManualCompletion(cmId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
|
||||||
|
return site.getDb().deleteRecords(CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE, {cmid: cmId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all offline manual completions for a certain course.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID the module belongs to.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved with the list of completions.
|
||||||
|
*/
|
||||||
|
getCourseManualCompletions(courseId: number, siteId?: string): Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
|
||||||
|
return site.getDb().getRecords(CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE, {courseid: courseId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the offline manual completion for a certain module.
|
||||||
|
*
|
||||||
|
* @param {number} cmId The module ID to remove the completion.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved with the completion, rejected if failure or not found.
|
||||||
|
*/
|
||||||
|
getManualCompletion(cmId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
|
||||||
|
return site.getDb().getRecord(CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE, {cmid: cmId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offline version for manually marking a module as completed.
|
||||||
|
*
|
||||||
|
* @param {number} cmId The module ID to store the completion.
|
||||||
|
* @param {number} completed Whether the module is completed or not.
|
||||||
|
* @param {number} courseId Course ID the module belongs to.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<{status: boolean, offline: boolean}>} Promise resolved when completion is successfully stored.
|
||||||
|
*/
|
||||||
|
markCompletedManually(cmId: number, completed: number, courseId: number, siteId?: string)
|
||||||
|
: Promise<{status: boolean, offline: boolean}> {
|
||||||
|
|
||||||
|
// Store the offline data.
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const entry = {
|
||||||
|
cmid: cmId,
|
||||||
|
completed: completed,
|
||||||
|
courseid: courseId,
|
||||||
|
timecreated: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.getDb().insertRecord(CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE, entry);
|
||||||
|
}).then(() => {
|
||||||
|
return {
|
||||||
|
status: true,
|
||||||
|
offline: true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreEventsProvider } from '@providers/events';
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
import { CoreLoggerProvider } from '@providers/logger';
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
@ -21,6 +22,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
import { CoreSiteWSPreSets } from '@classes/site';
|
import { CoreSiteWSPreSets } from '@classes/site';
|
||||||
import { CoreConstants } from '../../constants';
|
import { CoreConstants } from '../../constants';
|
||||||
|
import { CoreCourseOfflineProvider } from './course-offline';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that provides some features regarding a course.
|
* Service that provides some features regarding a course.
|
||||||
|
@ -75,7 +77,8 @@ export class CoreCourseProvider {
|
||||||
];
|
];
|
||||||
|
|
||||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private eventsProvider: CoreEventsProvider,
|
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private eventsProvider: CoreEventsProvider,
|
||||||
private utils: CoreUtilsProvider, private timeUtils: CoreTimeUtilsProvider, private translate: TranslateService) {
|
private utils: CoreUtilsProvider, private timeUtils: CoreTimeUtilsProvider, private translate: TranslateService,
|
||||||
|
private courseOffline: CoreCourseOfflineProvider, private appProvider: CoreAppProvider) {
|
||||||
this.logger = logger.getInstance('CoreCourseProvider');
|
this.logger = logger.getInstance('CoreCourseProvider');
|
||||||
|
|
||||||
this.sitesProvider.createTableFromSchema(this.courseStatusTableSchema);
|
this.sitesProvider.createTableFromSchema(this.courseStatusTableSchema);
|
||||||
|
@ -140,6 +143,27 @@ export class CoreCourseProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(null);
|
return Promise.reject(null);
|
||||||
|
}).then((completionStatus) => {
|
||||||
|
// Now get the offline completion (if any).
|
||||||
|
return this.courseOffline.getCourseManualCompletions(courseId, site.id).then((offlineCompletions) => {
|
||||||
|
offlineCompletions.forEach((offlineCompletion) => {
|
||||||
|
|
||||||
|
if (offlineCompletion && typeof completionStatus[offlineCompletion.cmid] != 'undefined') {
|
||||||
|
const onlineCompletion = completionStatus[offlineCompletion.cmid];
|
||||||
|
|
||||||
|
// If the activity uses manual completion, override the value with the offline one.
|
||||||
|
if (onlineCompletion.tracking === 1) {
|
||||||
|
onlineCompletion.state = offlineCompletion.completed;
|
||||||
|
onlineCompletion.offline = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return completionStatus;
|
||||||
|
}).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
return completionStatus;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -647,6 +671,85 @@ export class CoreCourseProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offline version for manually marking a module as completed.
|
||||||
|
*
|
||||||
|
* @param {number} cmId The module ID.
|
||||||
|
* @param {number} completed Whether the module is completed or not.
|
||||||
|
* @param {number} courseId Course ID the module belongs to.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when completion is successfully sent or stored.
|
||||||
|
*/
|
||||||
|
markCompletedManually(cmId: number, completed: number, courseId: number, siteId?: string): Promise<any> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
// Convenience function to store a message to be synchronized later.
|
||||||
|
const storeOffline = (): Promise<any> => {
|
||||||
|
return this.courseOffline.markCompletedManually(cmId, completed, courseId, siteId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if we already have a completion stored.
|
||||||
|
return this.courseOffline.getManualCompletion(cmId, siteId).catch(() => {
|
||||||
|
// No completion stored.
|
||||||
|
}).then((entry) => {
|
||||||
|
|
||||||
|
if (entry && completed != entry.completed) {
|
||||||
|
// It has changed, this means that the offline data can be deleted because the action was undone.
|
||||||
|
return this.courseOffline.deleteManualCompletion(cmId, siteId).then(() => {
|
||||||
|
return {
|
||||||
|
status: true,
|
||||||
|
offline: true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.appProvider.isOnline()) {
|
||||||
|
// App is offline, store the action.
|
||||||
|
return storeOffline();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.markCompletedManuallyOnline(cmId, completed, siteId).then((result) => {
|
||||||
|
// Data sent to server, if there is some offline data delete it now.
|
||||||
|
if (entry) {
|
||||||
|
return this.courseOffline.deleteManualCompletion(cmId, siteId).catch(() => {
|
||||||
|
// Ignore errors, shouldn't happen.
|
||||||
|
}).then(() => {
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}).catch((error) => {
|
||||||
|
if (this.utils.isWebServiceError(error)) {
|
||||||
|
// The WebService has thrown an error, this means that responses cannot be submitted.
|
||||||
|
return Promise.reject(error);
|
||||||
|
} else {
|
||||||
|
// Couldn't connect to server, store it offline.
|
||||||
|
return storeOffline();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offline version for manually marking a module as completed.
|
||||||
|
*
|
||||||
|
* @param {number} cmId The module ID.
|
||||||
|
* @param {number} completed Whether the module is completed or not.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when completion is successfully sent.
|
||||||
|
*/
|
||||||
|
markCompletedManuallyOnline(cmId: number, completed: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const params = {
|
||||||
|
cmid: cmId,
|
||||||
|
completed: completed
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.write('core_completion_update_activity_completion_status_manually', params);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the course status, setting it to the previous status.
|
* Change the course status, setting it to the previous status.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue