Merge pull request #2213 from dpalou/MOBILE-3213

Mobile 3213
main
Juan Leyva 2019-12-13 18:53:14 +01:00 committed by GitHub
commit fb1096381e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 112 additions and 64 deletions

View File

@ -156,7 +156,7 @@
<h2>{{ 'addon.badges.relatedbages' | translate}}</h2> <h2>{{ 'addon.badges.relatedbages' | translate}}</h2>
</ion-item-divider> </ion-item-divider>
<ion-item text-wrap *ngFor="let relatedBadge of badge.relatedbadges"> <ion-item text-wrap *ngFor="let relatedBadge of badge.relatedbadges">
<h2><{{ relatedBadge.name }}</h2> <h2>{{ relatedBadge.name }}</h2>
</ion-item> </ion-item>
<ion-item text-wrap *ngIf="badge.relatedbadges.length == 0"> <ion-item text-wrap *ngIf="badge.relatedbadges.length == 0">
<h2>{{ 'addon.badges.norelated' | translate}}</h2> <h2>{{ 'addon.badges.norelated' | translate}}</h2>

View File

@ -91,6 +91,12 @@ export class AddonBadgesProvider {
// In 3.7, competencies was renamed to alignment. Rename the property in 3.6 too. // In 3.7, competencies was renamed to alignment. Rename the property in 3.6 too.
response.badges.forEach((badge) => { response.badges.forEach((badge) => {
badge.alignment = badge.alignment || badge.competencies; badge.alignment = badge.alignment || badge.competencies;
// Check that the alignment is valid, they were broken in 3.7.
if (badge.alignment && badge.alignment[0] && typeof badge.alignment[0].targetname == 'undefined') {
// If any badge lacks targetname it means they are affected by the Moodle bug, don't display them.
delete badge.alignment;
}
}); });
return response.badges; return response.badges;
@ -175,20 +181,20 @@ export type AddonBadgesUserBadge = {
alignment?: { // @since 3.7. Calculated by the app for 3.6 sites. Badge alignments. alignment?: { // @since 3.7. Calculated by the app for 3.6 sites. Badge alignments.
id?: number; // Alignment id. id?: number; // Alignment id.
badgeid?: number; // Badge id. badgeid?: number; // Badge id.
targetName?: string; // Target name. targetname?: string; // Target name.
targetUrl?: string; // Target URL. targeturl?: string; // Target URL.
targetDescription?: string; // Target description. targetdescription?: string; // Target description.
targetFramework?: string; // Target framework. targetframework?: string; // Target framework.
targetCode?: string; // Target code. targetcode?: string; // Target code.
}[]; }[];
competencies?: { // @deprecated from 3.7. @since 3.6. In 3.7 it was renamed to alignment. competencies?: { // @deprecated from 3.7. @since 3.6. In 3.7 it was renamed to alignment.
id?: number; // Alignment id. id?: number; // Alignment id.
badgeid?: number; // Badge id. badgeid?: number; // Badge id.
targetName?: string; // Target name. targetname?: string; // Target name.
targetUrl?: string; // Target URL. targeturl?: string; // Target URL.
targetDescription?: string; // Target description. targetdescription?: string; // Target description.
targetFramework?: string; // Target framework. targetframework?: string; // Target framework.
targetCode?: string; // Target code. targetcode?: string; // Target code.
}[]; }[];
relatedbadges?: { // @since 3.6. Related badges. relatedbadges?: { // @since 3.6. Related badges.
id: number; // Badge id. id: number; // Badge id.

View File

@ -5,7 +5,7 @@
<core-loading [hideUntil]="loaded" class="core-loading-center"> <core-loading [hideUntil]="loaded" class="core-loading-center">
<ion-item *ngIf="showMyEntriesToggle"> <ion-item *ngIf="showMyEntriesToggle">
<ion-label>{{ 'addon.blog.showonlyyourentries' | translate }}</ion-label> <ion-label>{{ 'addon.blog.showonlyyourentries' | translate }}</ion-label>
<ion-toggle [(ngModel)]="onlyMyEntries" (ionChange)="onlyMyEntriesToggleChanged(onlyMyEntries)">></ion-toggle> <ion-toggle [(ngModel)]="onlyMyEntries" (ionChange)="onlyMyEntriesToggleChanged(onlyMyEntries)"></ion-toggle>
</ion-item> </ion-item>
<core-empty-box *ngIf="entries && entries.length == 0" icon="fa-newspaper-o" [message]="'addon.blog.noentriesyet' | translate"></core-empty-box> <core-empty-box *ngIf="entries && entries.length == 0" icon="fa-newspaper-o" [message]="'addon.blog.noentriesyet' | translate"></core-empty-box>
<ng-container *ngFor="let entry of entries"> <ng-container *ngFor="let entry of entries">

View File

@ -119,6 +119,25 @@ export class AddonModAssignProvider {
return assign.submissiondrafts; return assign.submissiondrafts;
} }
/**
* Fix some submission status params.
*
* @param site Site to use.
* @param userId User Id (empty for current user).
* @param groupId Group Id (empty for all participants).
* @param isBlind If blind marking is enabled or not.
* @return Object with fixed params.
*/
protected fixSubmissionStatusParams(site: CoreSite, userId?: number, groupId?: number, isBlind?: boolean)
: {userId: number, groupId: number, isBlind: boolean} {
return {
isBlind: !userId ? false : !!isBlind,
groupId: site.isVersionGreaterEqualThan('3.5') ? groupId || 0 : 0,
userId: userId || site.getUserId(),
};
}
/** /**
* Get an assignment by course module ID. * Get an assignment by course module ID.
* *
@ -502,24 +521,23 @@ export class AddonModAssignProvider {
getSubmissionStatus(assignId: number, userId?: number, groupId?: number, isBlind?: boolean, filter: boolean = true, getSubmissionStatus(assignId: number, userId?: number, groupId?: number, isBlind?: boolean, filter: boolean = true,
ignoreCache?: boolean, siteId?: string): Promise<AddonModAssignGetSubmissionStatusResult> { ignoreCache?: boolean, siteId?: string): Promise<AddonModAssignGetSubmissionStatusResult> {
userId = userId || 0;
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
groupId = site.isVersionGreaterEqualThan('3.5') ? groupId || 0 : 0; const fixedParams = this.fixSubmissionStatusParams(site, userId, groupId, isBlind);
const params = { const params = {
assignid: assignId, assignid: assignId,
userid: userId userid: fixedParams.userId
}, },
preSets: CoreSiteWSPreSets = { preSets: CoreSiteWSPreSets = {
cacheKey: this.getSubmissionStatusCacheKey(assignId, userId, groupId, isBlind), cacheKey: this.getSubmissionStatusCacheKey(assignId, fixedParams.userId, fixedParams.groupId,
fixedParams.isBlind),
getCacheUsingCacheKey: true, // We use the cache key to take isBlind into account. getCacheUsingCacheKey: true, // We use the cache key to take isBlind into account.
filter: filter, filter: filter,
rewriteurls: filter rewriteurls: filter
}; };
if (groupId) { if (fixedParams.groupId) {
params['groupid'] = groupId; params['groupid'] = fixedParams.groupId;
} }
if (ignoreCache) { if (ignoreCache) {
@ -578,11 +596,6 @@ export class AddonModAssignProvider {
* @return Cache key. * @return Cache key.
*/ */
protected getSubmissionStatusCacheKey(assignId: number, userId: number, groupId?: number, isBlind?: boolean): string { protected getSubmissionStatusCacheKey(assignId: number, userId: number, groupId?: number, isBlind?: boolean): string {
if (!userId) {
isBlind = false;
userId = this.sitesProvider.getCurrentSiteUserId();
}
return this.getSubmissionsCacheKey(assignId) + ':' + userId + ':' + (isBlind ? 1 : 0) + ':' + groupId; return this.getSubmissionsCacheKey(assignId) + ':' + userId + ':' + (isBlind ? 1 : 0) + ':' + groupId;
} }
@ -809,7 +822,10 @@ export class AddonModAssignProvider {
invalidateSubmissionStatusData(assignId: number, userId?: number, groupId?: number, isBlind?: boolean, siteId?: string): invalidateSubmissionStatusData(assignId: number, userId?: number, groupId?: number, isBlind?: boolean, siteId?: string):
Promise<any> { Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
return site.invalidateWsCacheForKey(this.getSubmissionStatusCacheKey(assignId, userId, groupId, isBlind)); const fixedParams = this.fixSubmissionStatusParams(site, userId, groupId, isBlind);
return site.invalidateWsCacheForKey(this.getSubmissionStatusCacheKey(assignId, fixedParams.userId,
fixedParams.groupId, fixedParams.isBlind));
}); });
} }

View File

@ -17,7 +17,7 @@
<ion-item text-wrap *ngIf="item.typ != 'pagebreak'" [color]="item.dependitem > 0 ? 'light' : ''" [class.core-danger-item]="item.isEmpty || item.hasError"> <ion-item text-wrap *ngIf="item.typ != 'pagebreak'" [color]="item.dependitem > 0 ? 'light' : ''" [class.core-danger-item]="item.isEmpty || item.hasError">
<ion-label *ngIf="item.name" [core-mark-required]="item.required" stacked> <ion-label *ngIf="item.name" [core-mark-required]="item.required" stacked>
<span *ngIf="feedback.autonumbering && item.itemnumber">{{item.itemnumber}}. </span> <span *ngIf="feedback.autonumbering && item.itemnumber">{{item.itemnumber}}. </span>
<core-format-text [component]="component" [componentId]="componentId" [text]="item.name" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text> <core-format-text [component]="component" [componentId]="componentId" [text]="item.name" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId" [wsNotFiltered]="true"></core-format-text>
<span *ngIf="item.postfix" class="addon-mod_feedback-postfix">{{item.postfix}}</span> <span *ngIf="item.postfix" class="addon-mod_feedback-postfix">{{item.postfix}}</span>
</ion-label> </ion-label>
<div item-content class="addon-mod_feedback-form-content" *ngIf="item.template"> <div item-content class="addon-mod_feedback-form-content" *ngIf="item.template">

View File

@ -105,7 +105,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
if (this.canGetFolder) { if (this.canGetFolder) {
promise = this.folderProvider.getFolder(this.courseId, this.module.id).then((folder) => { promise = this.folderProvider.getFolder(this.courseId, this.module.id).then((folder) => {
return this.courseProvider.loadModuleContents(this.module, this.courseId).then(() => { return this.courseProvider.loadModuleContents(this.module, this.courseId, undefined, false, refresh).then(() => {
folderContents = this.module.contents; folderContents = this.module.contents;
return folder; return folder;

View File

@ -53,7 +53,7 @@
<ion-icon name="trash"></ion-icon> <ion-icon name="trash"></ion-icon>
</button> </button>
</ion-item> </ion-item>
<ion-item text-wrap>{{ note.content }}</ion-item> <ion-item text-wrap><core-format-text [text]="note.content" [filter]="false"></core-format-text></ion-item>
</ion-card> </ion-card>
</ion-list> </ion-list>

View File

@ -180,18 +180,19 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
if (this.platform.is('android')) { if (this.platform.is('android')) {
// In Android we ignore the keyboard height because it is not part of the web view. // In Android we ignore the keyboard height because it is not part of the web view.
height = this.domUtils.getContentHeight(this.content) - this.getSurroundingHeight(this.element); height = this.domUtils.getContentHeight(this.content) - this.getSurroundingHeight(this.element);
} else if (this.platform.is('ios') && this.kbHeight > 0) { } else if (this.platform.is('ios') && this.kbHeight > 0 && this.platform.version().major < 12) {
// Keyboard open in iOS. // Keyboard open in iOS 11 or previous. The window height changes when the keyboard is open.
// In this case, the header disappears or is scrollable, so we need to adjust the calculations.
height = window.innerHeight - this.getSurroundingHeight(this.element); height = window.innerHeight - this.getSurroundingHeight(this.element);
if (this.element.getBoundingClientRect().top < 40) { if (this.element.getBoundingClientRect().top < 40) {
// In iOS sometimes the editor is placed below the status bar. Move the scroll a bit so it doesn't happen. // In iOS sometimes the editor is placed below the status bar. Move the scroll a bit so it doesn't happen.
window.scrollTo(window.scrollX, window.scrollY - 40); window.scrollTo(window.scrollX, window.scrollY - 40);
} }
} else { } else {
// Header is fixed, use the content to calculate the editor height. // Header is fixed, use the content to calculate the editor height.
height = this.domUtils.getContentHeight(this.content) - this.kbHeight - this.getSurroundingHeight(this.element); height = this.domUtils.getContentHeight(this.content) - this.kbHeight - this.getSurroundingHeight(this.element);
} }
if (height > this.minHeight) { if (height > this.minHeight) {
@ -549,13 +550,15 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
} }
/** /**
* Hide the toolbar. * Hide the toolbar in phone mode.
*/ */
hideToolbar($event: any): void { hideToolbar($event: any): void {
this.stopBubble($event); this.stopBubble($event);
if (this.isPhone) {
this.toolbarHidden = true; this.toolbarHidden = true;
} }
}
/** /**
* Show the toolbar. * Show the toolbar.

View File

@ -661,7 +661,6 @@ export class CoreCourseHelperProvider {
const mainFile = files[0], const mainFile = files[0],
fileUrl = this.fileHelper.getFileUrl(mainFile), fileUrl = this.fileHelper.getFileUrl(mainFile),
timemodified = this.fileHelper.getFileTimemodified(mainFile),
result = { result = {
fixedUrl: undefined, fixedUrl: undefined,
path: undefined, path: undefined,
@ -678,23 +677,60 @@ export class CoreCourseHelperProvider {
return this.filepoolProvider.getPackageStatus(siteId, component, componentId).then((status) => { return this.filepoolProvider.getPackageStatus(siteId, component, componentId).then((status) => {
result.status = status; result.status = status;
const isWifi = this.appProvider.isWifi(),
isOnline = this.appProvider.isOnline();
if (status === CoreConstants.DOWNLOADED) { if (status === CoreConstants.DOWNLOADED) {
// Get the local file URL. // Get the local file URL.
return this.filepoolProvider.getInternalUrlByUrl(siteId, fileUrl).catch((error) => { return this.filepoolProvider.getInternalUrlByUrl(siteId, fileUrl).catch((error) => {
// File not found, mark the module as not downloaded and reject. // File not found, mark the module as not downloaded and try again.
return this.filepoolProvider.storePackageStatus(siteId, CoreConstants.NOT_DOWNLOADED, component, return this.filepoolProvider.storePackageStatus(siteId, CoreConstants.NOT_DOWNLOADED, component,
componentId).then(() => { componentId).then(() => {
return Promise.reject(error); return this.downloadModuleWithMainFile(module, courseId, fixedUrl, files, status, component,
componentId, siteId);
}); });
}); });
} else if (status === CoreConstants.DOWNLOADING && !this.appProvider.isDesktop()) { } else if (status === CoreConstants.DOWNLOADING && !this.appProvider.isDesktop()) {
// Return the online URL. // Return the online URL.
return fixedUrl; return fixedUrl;
} else { } else {
return this.downloadModuleWithMainFile(module, courseId, fixedUrl, files, status, component, componentId,
siteId);
}
}).then((path) => {
result.path = path;
return result;
});
} else {
// We use the live URL.
result.path = fixedUrl;
return result;
}
});
}
/**
* Convenience function to download a module that has a main file and return the local file's path and other info.
* This is meant for modules like mod_resource.
*
* @param module The module to download.
* @param courseId The course ID of the module.
* @param fixedUrl Main file's fixed URL.
* @param files List of files of the module.
* @param status The package status.
* @param component The component to link the files to.
* @param componentId An ID to use in conjunction with the component.
* @param siteId The site ID. If not defined, current site.
* @return Promise resolved when done.
*/
protected downloadModuleWithMainFile(module: any, courseId: number, fixedUrl: string, files: any[], status: string,
component?: string, componentId?: string | number, siteId?: string): Promise<string> {
const isOnline = this.appProvider.isOnline();
const mainFile = files[0];
const fileUrl = this.fileHelper.getFileUrl(mainFile);
const timemodified = this.fileHelper.getFileTimemodified(mainFile);
if (!isOnline && status === CoreConstants.NOT_DOWNLOADED) { if (!isOnline && status === CoreConstants.NOT_DOWNLOADED) {
// Not downloaded and we're offline, reject. // Not downloaded and we're offline, reject.
return Promise.reject(this.translate.instant('core.networkerrormsg')); return Promise.reject(this.translate.instant('core.networkerrormsg'));
@ -707,7 +743,7 @@ export class CoreCourseHelperProvider {
}); });
}, () => { }, () => {
// Start the download if in wifi, but return the URL right away so the file is opened. // Start the download if in wifi, but return the URL right away so the file is opened.
if (isWifi) { if (this.appProvider.isWifi()) {
this.downloadModule(module, courseId, component, componentId, files, siteId); this.downloadModule(module, courseId, component, componentId, files, siteId);
} }
@ -721,19 +757,6 @@ export class CoreCourseHelperProvider {
} }
}); });
} }
}).then((path) => {
result.path = path;
return result;
});
} else {
// We use the live URL.
result.path = fixedUrl;
return result;
}
});
}
/** /**
* Convenience function to download a module. * Convenience function to download a module.