Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/langindex.json
Original file line number Diff line number Diff line change
Expand Up @@ -1608,6 +1608,7 @@
"core.remove": "moodle",
"core.required": "moodle",
"core.requireduserdatamissing": "local_moodlemobileapp",
"core.resourcedisplayopen": "moodle",
"core.resources": "moodle",
"core.restore": "moodle",
"core.retry": "local_moodlemobileapp",
Expand Down
2 changes: 2 additions & 0 deletions src/addon/mod/feedback/components/index/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
@Input() tab = 'overview';
@Input() group = 0;

component = AddonModFeedbackProvider.COMPONENT;
moduleName = 'feedback';

access = {
Expand Down Expand Up @@ -67,6 +68,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
firstSelectedTab: number;

protected submitObserver: any;
protected syncEventName = AddonModFeedbackSyncProvider.AUTO_SYNCED;

constructor(injector: Injector, private feedbackProvider: AddonModFeedbackProvider, @Optional() content: Content,
private feedbackOffline: AddonModFeedbackOfflineProvider, private groupsProvider: CoreGroupsProvider,
Expand Down
2 changes: 1 addition & 1 deletion src/addon/mod/forum/pages/discussion/discussion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ export class AddonModForumDiscussionPage implements OnDestroy {
protected syncDiscussion(showErrors: boolean): Promise<any> {
const promises = [];

promises.push(this.forumSync.syncDiscussionReplies(this.forumId, this.discussionId).then((result) => {
promises.push(this.forumSync.syncDiscussionReplies(this.discussionId).then((result) => {
if (result.warnings && result.warnings.length) {
this.domUtils.showErrorModal(result.warnings[0]);
}
Expand Down
89 changes: 50 additions & 39 deletions src/addon/mod/lesson/components/index/addon-mod-lesson-index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,53 +51,64 @@
</ion-card>

<core-loading [hideUntil]="!showSpinner">
<ion-list *ngIf="lesson && (!preventMessages || !preventMessages.length)">

<ion-list *ngIf="(lesson && (!preventMessages || !preventMessages.length)) || retakeToReview">
<ion-item text-wrap *ngIf="retakeToReview">
<!-- A retake was finished in a synchronization, allow reviewing it. -->
<p>{{ 'addon.mod_lesson.retakefinishedinsync' | translate }}</p>
<p padding-bottom>{{ 'addon.mod_lesson.retakefinishedinsync' | translate }}</p>
<a ion-button block (click)="review()">{{ 'addon.mod_lesson.review' | translate }}</a>
</ion-item>

<ion-item text-wrap *ngIf="leftDuringTimed && !lesson.timelimit">
<!-- User left during the session and there is no time limit, ask to continue. -->
<p [innerHTML]="'addon.mod_lesson.youhaveseen' | translate"></p>
<ion-grid>
<ion-row>
<ion-col>
<a ion-button block color="light" (click)="start(false)">{{ 'core.no' | translate }}</a>
</ion-col>
<ion-col>
<a ion-button block (click)="start(true)">{{ 'core.yes' | translate }}</a>
</ion-col>
</ion-row>
</ion-grid>
</ion-item>
<ng-container *ngIf="lesson && (!preventMessages || !preventMessages.length)">
<ion-item text-wrap *ngIf="leftDuringTimed && !lesson.timelimit && !finishedOffline">
<!-- User left during the session and there is no time limit, ask to continue. -->
<p [innerHTML]="'addon.mod_lesson.youhaveseen' | translate"></p>
<ion-grid>
<ion-row>
<ion-col>
<a ion-button block color="light" (click)="start(false)">{{ 'core.no' | translate }}</a>
</ion-col>
<ion-col>
<a ion-button block (click)="start(true)">{{ 'core.yes' | translate }}</a>
</ion-col>
</ion-row>
</ion-grid>
</ion-item>

<ion-item text-wrap *ngIf="leftDuringTimed && lesson.timelimit && lesson.retake">
<!-- User left during the session with time limit and retakes allowed, ask to continue. -->
<p [innerHTML]="'addon.mod_lesson.leftduringtimed' | translate"></p>
<a ion-button block icon-end (click)="start(false)">
{{ 'addon.mod_lesson.continue' | translate }}
<ion-icon name="arrow-forward" md="ios-arrow-forward"></ion-icon>
</a>
</ion-item>
<ion-item text-wrap *ngIf="leftDuringTimed && lesson.timelimit && lesson.retake && !finishedOffline">
<!-- User left during the session with time limit and retakes allowed, ask to continue. -->
<p [innerHTML]="'addon.mod_lesson.leftduringtimed' | translate"></p>
<a ion-button block icon-end (click)="start(false)">
{{ 'addon.mod_lesson.continue' | translate }}
<ion-icon name="arrow-forward" md="ios-arrow-forward"></ion-icon>
</a>
</ion-item>

<ion-item text-wrap *ngIf="leftDuringTimed && lesson.timelimit && !lesson.retake">
<!-- User left during the session with time limit and retakes not allowed. This should be handled by preventMessages -->
<p [innerHTML]="'addon.mod_lesson.leftduringtimednoretake' | translate"></p>
</ion-item>
<ion-item text-wrap *ngIf="leftDuringTimed && lesson.timelimit && !lesson.retake">
<!-- User left during the session with time limit and retakes not allowed. This should be handled by preventMessages -->
<p [innerHTML]="'addon.mod_lesson.leftduringtimednoretake' | translate"></p>
</ion-item>

<ion-item text-wrap *ngIf="!leftDuringTimed">
<!-- User hasn't left during the session, show a start button. -->
<a ion-button block *ngIf="!canManage" icon-end (click)="start(false)">
{{ 'core.start' | translate }}
<ion-icon name="arrow-forward" md="ios-arrow-forward"></ion-icon>
</a>
<a ion-button block *ngIf="canManage" icon-end (click)="start(false)">
{{ 'addon.mod_lesson.preview' | translate }}
<ion-icon name="search"></ion-icon>
</a>
</ion-item>
<ion-item text-wrap *ngIf="!leftDuringTimed && !finishedOffline">
<!-- User hasn't left during the session, show a start button. -->
<a ion-button block *ngIf="!canManage" icon-end (click)="start(false)">
{{ 'core.start' | translate }}
<ion-icon name="arrow-forward" md="ios-arrow-forward"></ion-icon>
</a>
<a ion-button block *ngIf="canManage" icon-end (click)="start(false)">
{{ 'addon.mod_lesson.preview' | translate }}
<ion-icon name="search"></ion-icon>
</a>
</ion-item>

<ion-item text-wrap *ngIf="finishedOffline">
<!-- There's an attempt finished in offline. Let the user continue, showing the end of lesson. -->
<a ion-button block icon-end (click)="start(true)">
{{ 'addon.mod_lesson.continue' | translate }}
<ion-icon name="arrow-forward" md="ios-arrow-forward"></ion-icon>
</a>
</ion-item>
</ng-container>
</ion-list>
</core-loading>
</ng-template>
Expand Down
6 changes: 6 additions & 0 deletions src/addon/mod/lesson/components/index/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
reportLoaded: boolean; // Whether the report data has been loaded.
selectedGroupName: string; // The name of the selected group.
overview: any; // Reports overview data.
finishedOffline: boolean; // Whether a retake was finished in offline.

protected syncEventName = AddonModLessonSyncProvider.AUTO_SYNCED;
protected accessInfo: any; // Lesson access info.
Expand Down Expand Up @@ -159,6 +160,11 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
}
}));

// Check if the ser has a finished retake in offline.
promises.push(this.lessonOffline.hasFinishedRetake(this.lesson.id).then((finished) => {
this.finishedOffline = finished;
}));

// Update the list of content pages viewed and question attempts.
promises.push(this.lessonProvider.getContentPagesViewedOnline(this.lesson.id, info.attemptscount));
promises.push(this.lessonProvider.getQuestionsAttemptsOnline(this.lesson.id, info.attemptscount));
Expand Down
4 changes: 2 additions & 2 deletions src/addon/mod/lesson/pages/player/player.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@
<!-- Page content. -->
<ion-card *ngIf="!eolData && !processData">
<!-- Content page. -->
<ion-item text-wrap *ngIf="!question">
<ion-item text-wrap *ngIf="!question && pageContent">
<core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="pageContent"></core-format-text>
</ion-item>

<!-- Question page. -->
<!-- We need to set ngIf loaded to make formGroup directive restart every time a page changes, see MOBILE-2540. -->
<form *ngIf="question && loaded" ion-list [formGroup]="questionForm">
<ion-item-divider text-wrap>
<ion-item-divider text-wrap *ngIf="pageContent">
<core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="pageContent"></core-format-text>
</ion-item-divider>

Expand Down
14 changes: 11 additions & 3 deletions src/addon/mod/lesson/providers/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import { AddonModLessonProvider } from './lesson';
export class AddonModLessonHelperProvider {

constructor(private domUtils: CoreDomUtilsProvider, private fb: FormBuilder, private translate: TranslateService,
private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { }
private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider,
private lessonProvider: AddonModLessonProvider) { }

/**
* Given the HTML of next activity link, format it to extract the href and the text.
Expand Down Expand Up @@ -149,8 +150,15 @@ export class AddonModLessonHelperProvider {
return contents.innerHTML.trim();
}

// Cannot find contents element, return the page.contents (some elements like videos might not work).
return data.page.contents;
// Cannot find contents element.
if (this.lessonProvider.isQuestionPage(data.page.type) ||
data.page.qtype == AddonModLessonProvider.LESSON_PAGE_BRANCHTABLE) {
// Return page.contents to prevent having duplicated elements (some elements like videos might not work).
return data.page.contents;
} else {
// It's an end of cluster, end of branch, etc. Return the whole pagecontent to match what's displayed in web.
return data.pagecontent;
}
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/addon/mod/workshop/components/index/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { AddonModWorkshopOfflineProvider } from '../../providers/offline';
export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivityComponent {
@Input() group = 0;

component = AddonModWorkshopProvider.COMPONENT;
moduleName = 'workshop';
workshop: any;
page = 0;
Expand Down Expand Up @@ -64,6 +65,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
protected obsAssessmentSaved: any;
protected appResumeSubscription: any;
protected syncObserver: any;
protected syncEventName = AddonModWorkshopSyncProvider.AUTO_SYNCED;

constructor(injector: Injector, private workshopProvider: AddonModWorkshopProvider, @Optional() content: Content,
private workshopOffline: AddonModWorkshopOfflineProvider, private groupsProvider: CoreGroupsProvider,
Expand Down
1 change: 1 addition & 0 deletions src/assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1608,6 +1608,7 @@
"core.remove": "Remove",
"core.required": "Required",
"core.requireduserdatamissing": "This user lacks some required profile data. Please enter the data in your site and try again.<br>{{$a}}",
"core.resourcedisplayopen": "Open",
"core.resources": "Resources",
"core.restore": "Restore",
"core.retry": "Retry",
Expand Down
8 changes: 3 additions & 5 deletions src/classes/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ export class CoreSite {

// Session expired, trigger event.
this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {}, this.id);
// Change error message. We'll try to get data from cache.
// Change error message. Try to get data from cache, the event will handle the error.
error.message = this.translate.instant('core.lostconnection');
} else if (error.errorcode === 'userdeleted') {
// User deleted, trigger event.
Expand All @@ -614,17 +614,15 @@ export class CoreSite {

return Promise.reject(error);
} else if (error.errorcode === 'forcepasswordchangenotice') {
// Password Change Forced, trigger event.
// Password Change Forced, trigger event. Try to get data from cache, the event will handle the error.
this.eventsProvider.trigger(CoreEventsProvider.PASSWORD_CHANGE_FORCED, {}, this.id);
error.message = this.translate.instant('core.forcepasswordchangenotice');

return Promise.reject(error);
} else if (error.errorcode === 'usernotfullysetup') {
// User not fully setup, trigger event.
// User not fully setup, trigger event. Try to get data from cache, the event will handle the error.
this.eventsProvider.trigger(CoreEventsProvider.USER_NOT_FULLY_SETUP, {}, this.id);
error.message = this.translate.instant('core.usernotfullysetup');

return Promise.reject(error);
} else if (error.errorcode === 'sitepolicynotagreed') {
// Site policy not agreed, trigger event.
this.eventsProvider.trigger(CoreEventsProvider.SITE_POLICY_NOT_AGREED, {}, this.id);
Expand Down
14 changes: 7 additions & 7 deletions src/components/local-file/local-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,6 @@ export class CoreLocalFileComponent implements OnInit {
ngOnInit(): void {
this.manage = this.utils.isTrueOrOne(this.manage);

// Let's calculate the relative path for the file.
this.relativePath = this.fileProvider.removeBasePath(this.file.toURL());
if (!this.relativePath) {
// Didn't find basePath, use fullPath but if the user tries to manage the file it'll probably fail.
this.relativePath = this.file.fullPath;
}

this.loadFileBasicData();

// Get the size and timemodified.
Expand All @@ -88,6 +81,13 @@ export class CoreLocalFileComponent implements OnInit {
this.fileName = this.file.name;
this.fileIcon = this.mimeUtils.getFileIcon(this.file.name);
this.fileExtension = this.mimeUtils.getFileExtension(this.file.name);

// Let's calculate the relative path for the file.
this.relativePath = this.fileProvider.removeBasePath(this.file.toURL());
if (!this.relativePath) {
// Didn't find basePath, use fullPath but if the user tries to manage the file it'll probably fail.
this.relativePath = this.file.fullPath;
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/components/recaptcha/core-recaptcha.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div *ngIf="publicKey">
<!-- A button to open the recaptcha modal. -->
<!-- Use anchor instead of button to prevent marking form as submitted. -->
<button ion-button block *ngIf="!model[modelValueName]" (click)="answerRecaptcha()" type="button">{{ 'core.answer' | translate }}</button>
<button ion-button block *ngIf="!model[modelValueName]" (click)="answerRecaptcha()" type="button">{{ 'core.resourcedisplayopen' | translate }}</button>
<p *ngIf="model[modelValueName]" class="text-success">{{ 'core.answered' | translate }}</p>
<p *ngIf="expired" class="text-danger">{{ 'core.login.recaptchaexpired' | translate }}</p>
</div>
Expand Down
28 changes: 18 additions & 10 deletions src/components/split-view/split-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class CoreSplitViewComponent implements OnInit, OnDestroy {
@ViewChild('menu') menu: Menu;
@Input() when?: string | boolean = 'md';

protected isEnabled = false;
protected isEnabled;
protected masterPageName = '';
protected masterPageIndex = 0;
protected loadDetailPage: any = false;
Expand Down Expand Up @@ -174,24 +174,32 @@ export class CoreSplitViewComponent implements OnInit, OnDestroy {
* @return {boolean} If split view is enabled.
*/
isOn(): boolean {
return this.isEnabled;
return !!this.isEnabled;
}

/**
* Push a page to the navigation stack. It will decide where to load it depending on the size of the screen.
*
* @param {any} page The component class or deeplink name you want to push onto the navigation stack.
* @param {any} params Any NavParams you want to pass along to the next view.
* @param {boolean} [retrying] Whether it's retrying.
*/
push(page: any, params?: any): void {
if (this.isEnabled) {
this.detailNav.setRoot(page, params);
push(page: any, params?: any, retrying?: boolean): void {
if (typeof this.isEnabled == 'undefined' && !retrying) {
// Hasn't calculated if it's enabled yet. Wait a bit and try again.
setTimeout(() => {
this.push(page, params, true);
}, 200);
} else {
this.loadDetailPage = {
component: page,
data: params
};
this.masterNav.push(page, params);
if (this.isEnabled) {
this.detailNav.setRoot(page, params);
} else {
this.loadDetailPage = {
component: page,
data: params
};
this.masterNav.push(page, params);
}
}
}

Expand Down
4 changes: 0 additions & 4 deletions src/core/emulator/providers/local-notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,6 @@ export class LocalNotificationsMock extends LocalNotifications {
notification.timeoutAfter = this.parseToInt('timeoutAfter', notification);
}

if (typeof notification.data == 'object') {
notification.data = JSON.stringify(notification.data);
}

this.convertPriority(notification);
this.convertTrigger(notification);
this.convertActions(notification);
Expand Down
Loading