Merge pull request #3841 from dpalou/MOBILE-4362

Mobile 4362
main
Pau Ferrer Ocaña 2023-10-31 16:00:42 +01:00 committed by GitHub
commit 9ff408d3b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 91 additions and 30 deletions

View File

@ -176,6 +176,8 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
* @inheritdoc * @inheritdoc
*/ */
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
super.ngOnChanges(changes);
if (this.loaded && changes.block) { if (this.loaded && changes.block) {
// Block was re-fetched, load content. // Block was re-fetched, load content.
this.reloadContent(); this.reloadContent();

View File

@ -1089,11 +1089,11 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
// Only show outcomes with info on it, outcomeid could be null if outcomes are disabled on site. // Only show outcomes with info on it, outcomeid could be null if outcomes are disabled on site.
gradeInfo.outcomes?.forEach((outcome) => { gradeInfo.outcomes?.forEach((outcome) => {
if (outcome.id == String(grade.outcomeid)) { if (outcome.id == String(grade.outcomeid)) {
outcome.selected = grade.gradeformatted; // Clean HTML tags, grade can contain an icon.
outcome.selected = CoreTextUtils.cleanTags(grade.gradeformatted || '');
outcome.modified = grade.gradedategraded; outcome.modified = grade.gradedategraded;
if (outcome.options) { if (outcome.options) {
outcome.selectedId = outcome.selectedId = CoreGradesHelper.getGradeValueFromLabel(outcome.options, outcome.selected);
CoreGradesHelper.getGradeValueFromLabel(outcome.options, outcome.selected || '');
this.originalGrades.outcomes[outcome.id] = outcome.selectedId; this.originalGrades.outcomes[outcome.id] = outcome.selectedId;
outcome.itemNumber = grade.itemnumber; outcome.itemNumber = grade.itemnumber;
} }

View File

@ -7,7 +7,10 @@
<ion-item *ngIf="model[modelValueName]"> <ion-item *ngIf="model[modelValueName]">
<ion-label color="success">{{ 'core.answered' | translate }}</ion-label> <ion-label color="success">{{ 'core.answered' | translate }}</ion-label>
</ion-item> </ion-item>
<ion-item *ngIf="expired" class="ion-text-wrap"> <ion-item *ngIf="expired" class="ion-text-wrap core-input-error">
<ion-label color="danger">{{ 'core.login.recaptchaexpired' | translate }}</ion-label> <ion-label color="danger">{{ 'core.login.recaptchaexpired' | translate }}</ion-label>
</ion-item> </ion-item>
<ion-item *ngIf="showRequiredError && !model[modelValueName] && !expired" class="ion-text-wrap core-input-error">
<ion-label color="danger">{{ 'core.required' | translate }}</ion-label>
</ion-item>
</div> </div>

View File

@ -32,6 +32,7 @@ export class CoreRecaptchaComponent implements OnInit {
@Input() publicKey?: string; // The site public key. @Input() publicKey?: string; // The site public key.
@Input() modelValueName = 'recaptcharesponse'; // Name of the model property where to store the response. @Input() modelValueName = 'recaptcharesponse'; // Name of the model property where to store the response.
@Input() siteUrl = ''; // The site URL. If not defined, current site. @Input() siteUrl = ''; // The site URL. If not defined, current site.
@Input() showRequiredError = false; // Whether to display the required error if recaptcha hasn't been answered.
expired = false; expired = false;

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { OnInit, Input, Component, Optional, Inject } from '@angular/core'; import { OnInit, Input, Component, Optional, Inject, OnChanges, SimpleChanges } from '@angular/core';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
@ -30,7 +30,7 @@ import { CorePromisedValue } from '@classes/promised-value';
@Component({ @Component({
template: '', template: '',
}) })
export abstract class CoreBlockBaseComponent implements OnInit, ICoreBlockComponent, AsyncDirective { export abstract class CoreBlockBaseComponent implements OnInit, OnChanges, ICoreBlockComponent, AsyncDirective {
@Input() title!: string; // The block title. @Input() title!: string; // The block title.
@Input() block!: CoreCourseBlock; // The block to render. @Input() block!: CoreCourseBlock; // The block to render.
@ -54,15 +54,31 @@ export abstract class CoreBlockBaseComponent implements OnInit, ICoreBlockCompon
* @inheritdoc * @inheritdoc
*/ */
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
if (this.block.configs && this.block.configs.length > 0) { await this.loadContent();
this.block.configs.forEach((config) => { }
config.value = CoreTextUtils.parseJSON(config.value);
});
this.block.configsRecord = CoreUtils.arrayToObject(this.block.configs, 'name'); /**
* @inheritdoc
*/
ngOnChanges(changes: SimpleChanges): void {
if (changes.block) {
this.parseConfigs();
}
}
/**
* Parse configs if needed.
*/
protected parseConfigs(): void {
if (!this.block.configs?.length || this.block.configsRecord) {
return;
} }
await this.loadContent(); this.block.configs.forEach((config) => {
config.value = CoreTextUtils.parseJSON(config.value);
});
this.block.configsRecord = CoreUtils.arrayToObject(this.block.configs, 'name');
} }
/** /**

View File

@ -193,7 +193,6 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
this.siteName = this.siteConfig.sitename; this.siteName = this.siteConfig.sitename;
this.logoUrl = CoreLoginHelper.getLogoUrl(this.siteConfig); this.logoUrl = CoreLoginHelper.getLogoUrl(this.siteConfig);
this.authInstructions = this.siteConfig.authinstructions || Translate.instant('core.login.loginsteps');
this.showScanQR = await CoreLoginHelper.displayQRInCredentialsScreen(this.siteConfig.tool_mobile_qrcodetype); this.showScanQR = await CoreLoginHelper.displayQRInCredentialsScreen(this.siteConfig.tool_mobile_qrcodetype);
const disabledFeatures = CoreLoginHelper.getDisabledFeatures(this.siteConfig); const disabledFeatures = CoreLoginHelper.getDisabledFeatures(this.siteConfig);
@ -204,6 +203,8 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
!!this.supportConfig?.canContactSupport(), !!this.supportConfig?.canContactSupport(),
this.showForgottenPassword, this.showForgottenPassword,
); );
this.authInstructions = this.siteConfig.authinstructions ||
(this.canSignup ? Translate.instant('core.login.loginsteps') : '');
if (!this.eventThrown && !this.viewLeft) { if (!this.eventThrown && !this.viewLeft) {
this.eventThrown = true; this.eventThrown = true;

View File

@ -21,13 +21,13 @@
<div class="list-item-limited-width"> <div class="list-item-limited-width">
<!-- Site has an unsupported required field. --> <!-- Site has an unsupported required field. -->
<ion-list *ngIf="!allRequiredSupported"> <ion-list *ngIf="!allRequiredSupported" class="ion-padding">
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
{{ 'core.login.signuprequiredfieldnotsupported' | translate }} {{ 'core.login.signuprequiredfieldnotsupported' | translate }}
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-button expand="block" class="ion-margin" [href]="signupUrl" core-link autoLogin="no" [showBrowserWarning]="false"> <ion-button expand="block" [href]="signupUrl" core-link autoLogin="no" [showBrowserWarning]="false">
{{ 'core.openinbrowser' | translate }} {{ 'core.openinbrowser' | translate }}
</ion-button> </ion-button>
</ion-list> </ion-list>
@ -62,9 +62,11 @@
</ion-item> </ion-item>
<!-- Submit button. --> <!-- Submit button. -->
<ion-button expand="block" class="ion-margin" type="submit" [disabled]="!ageVerificationForm.valid"> <div class="ion-padding">
{{ 'core.proceed' | translate }} <ion-button expand="block" type="submit" [disabled]="!ageVerificationForm.valid">
</ion-button> {{ 'core.proceed' | translate }}
</ion-button>
</div>
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
@ -186,7 +188,8 @@
<h2><span [core-mark-required]="true">{{ 'core.login.security_question' | translate }}</span></h2> <h2><span [core-mark-required]="true">{{ 'core.login.security_question' | translate }}</span></h2>
</ion-label> </ion-label>
</ion-item-divider> </ion-item-divider>
<core-recaptcha [publicKey]="settings.recaptchapublickey" [model]="captcha" [siteUrl]="siteUrl"></core-recaptcha> <core-recaptcha [publicKey]="settings.recaptchapublickey" [model]="captcha" [siteUrl]="siteUrl"
[showRequiredError]="formSubmitClicked"></core-recaptcha>
</ng-container> </ng-container>
<!-- Site policy (if any). --> <!-- Site policy (if any). -->
@ -213,10 +216,12 @@
</ion-item> </ion-item>
</ng-container> </ng-container>
<!-- Submit button. --> <div class="ion-padding">
<ion-button expand="block" class="ion-margin" type="submit">{{ 'core.login.createaccount' | translate }}</ion-button> <!-- Submit button. -->
<!-- Remove this once Ionic fixes this bug: https://github.com/ionic-team/ionic-framework/issues/19368 --> <ion-button expand="block" type="submit">{{ 'core.login.createaccount' | translate }}</ion-button>
<input type="submit" class="core-submit-hidden-enter" /> <!-- Remove this once Ionic fixes this bug: https://github.com/ionic-team/ionic-framework/issues/19368 -->
<input type="submit" class="core-submit-hidden-enter" />
</div>
</form> </form>
</ng-container> </ng-container>
</div> </div>
@ -239,9 +244,11 @@
<p *ngIf="supportEmail">{{ supportEmail }}</p> <p *ngIf="supportEmail">{{ supportEmail }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-button *ngIf="!supportName && !supportEmail" expand="block" class="ion-margin" (click)="showContactOnSite()"> <div class="ion-padding">
{{ 'core.openinbrowser' | translate }} <ion-button *ngIf="!supportName && !supportEmail" expand="block" (click)="showContactOnSite()">
</ion-button> {{ 'core.openinbrowser' | translate }}
</ion-button>
</div>
</ion-list> </ion-list>
</div> </div>
</ion-content> </ion-content>

View File

@ -60,6 +60,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
settingsLoaded = false; settingsLoaded = false;
allRequiredSupported = true; allRequiredSupported = true;
signupUrl?: string; signupUrl?: string;
formSubmitClicked = false;
captcha = { captcha = {
recaptcharesponse: '', recaptcharesponse: '',
}; };
@ -265,6 +266,8 @@ export class CoreLoginEmailSignupPage implements OnInit {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.formSubmitClicked = true;
if (!this.signupForm.valid || (this.settings?.recaptchapublickey && !this.captcha.recaptcharesponse)) { if (!this.signupForm.valid || (this.settings?.recaptchapublickey && !this.captcha.recaptcharesponse)) {
// Form not valid. Mark all controls as dirty to display errors. // Form not valid. Mark all controls as dirty to display errors.
for (const name in this.signupForm.controls) { for (const name in this.signupForm.controls) {

View File

@ -75,7 +75,7 @@
</ion-label> </ion-label>
<ion-toggle [(ngModel)]="debugDisplay" (ionChange)="debugDisplayChanged($event)" slot="end"></ion-toggle> <ion-toggle [(ngModel)]="debugDisplay" (ionChange)="debugDisplayChanged($event)" slot="end"></ion-toggle>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="analyticsSupported"> <ion-item class="ion-text-wrap" *ngIf="analyticsAvailable">
<ion-label> <ion-label>
<p class="item-heading">{{ 'core.settings.enableanalytics' | translate }}</p> <p class="item-heading">{{ 'core.settings.enableanalytics' | translate }}</p>
<p>{{ 'core.settings.enableanalyticsdescription' | translate }}</p> <p>{{ 'core.settings.enableanalyticsdescription' | translate }}</p>

View File

@ -44,7 +44,7 @@ export class CoreSettingsGeneralPage {
selectedZoomLevel = CoreZoomLevel.NONE; selectedZoomLevel = CoreZoomLevel.NONE;
richTextEditor = true; richTextEditor = true;
debugDisplay = false; debugDisplay = false;
analyticsSupported = false; analyticsAvailable = false;
analyticsEnabled = false; analyticsEnabled = false;
colorSchemes: CoreColorScheme[] = []; colorSchemes: CoreColorScheme[] = [];
selectedScheme: CoreColorScheme = CoreColorScheme.LIGHT; selectedScheme: CoreColorScheme = CoreColorScheme.LIGHT;
@ -101,8 +101,8 @@ export class CoreSettingsGeneralPage {
this.debugDisplay = await CoreConfig.get(CoreConstants.SETTINGS_DEBUG_DISPLAY, false); this.debugDisplay = await CoreConfig.get(CoreConstants.SETTINGS_DEBUG_DISPLAY, false);
this.analyticsSupported = CoreAnalytics.hasHandlers(); this.analyticsAvailable = await CoreAnalytics.isAnalyticsAvailable();
if (this.analyticsSupported) { if (this.analyticsAvailable) {
this.analyticsEnabled = await CoreConfig.get(CoreConstants.SETTINGS_ANALYTICS_ENABLED, true); this.analyticsEnabled = await CoreConfig.get(CoreConstants.SETTINGS_ANALYTICS_ENABLED, true);
} }

View File

@ -57,6 +57,29 @@ export class CoreAnalyticsService extends CoreDelegate<CoreAnalyticsHandler> {
} }
} }
/**
* Check if analytics is available for the app/site.
*
* @returns True if available, false otherwise.
*/
async isAnalyticsAvailable(): Promise<boolean> {
if (Object.keys(this.enabledHandlers).length > 0) {
// There is an enabled handler, analytics is available.
return true;
}
// Check if there is a handler that is enabled at app level (enabled handlers are only set when logged in).
const enabledList = await Promise.all(Object.values(this.handlers).map(handler => {
if (!handler.appLevelEnabled) {
return false;
}
return handler.isEnabled();
}));
return enabledList.includes(true);
}
/** /**
* Log an event for the current site. * Log an event for the current site.
* *
@ -108,6 +131,11 @@ export const CoreAnalytics = makeSingleton(CoreAnalyticsService);
*/ */
export interface CoreAnalyticsHandler extends CoreDelegateHandler { export interface CoreAnalyticsHandler extends CoreDelegateHandler {
/**
* If true it means that the handler is enabled or not for the whole app, it doesn't depend on the site.
*/
appLevelEnabled?: boolean;
/** /**
* Log an event. * Log an event.
* *