MOBILE-2768 policy: Adapt the UI of accept policy page

main
Dani Palou 2024-02-13 13:03:30 +01:00
parent d7ce967746
commit c7262f715e
6 changed files with 146 additions and 50 deletions

View File

@ -2282,7 +2282,7 @@
"core.phone": "moodle", "core.phone": "moodle",
"core.pictureof": "moodle", "core.pictureof": "moodle",
"core.play": "local_moodlemobileapp", "core.play": "local_moodlemobileapp",
"core.policy.policyaccept": "moodle", "core.policy.havereadandagreepolicy": "local_moodlemobileapp",
"core.policy.policyacceptmandatory": "local_moodlemobileapp", "core.policy.policyacceptmandatory": "local_moodlemobileapp",
"core.policy.policyagree": "moodle", "core.policy.policyagree": "moodle",
"core.policy.policyagreement": "moodle", "core.policy.policyagreement": "moodle",

View File

@ -1,5 +1,5 @@
{ {
"policyaccept": "I understand and agree", "havereadandagreepolicy": "I have read and agree to the {{policyname}}",
"policyacceptmandatory": "I understand and agree to the mandatory site policies", "policyacceptmandatory": "I understand and agree to the mandatory site policies",
"policyagree": "You must agree to this policy to continue using this site. Do you agree?", "policyagree": "You must agree to this policy to continue using this site. Do you agree?",
"policyagreement": "Site policy agreement", "policyagreement": "Site policy agreement",

View File

@ -4,35 +4,62 @@
<ion-back-button [text]="'core.back' | translate" /> <ion-back-button [text]="'core.back' | translate" />
</ion-buttons> </ion-buttons>
<ion-title> <ion-title *ngIf="siteName">
<h1>{{ 'core.policy.policyagreement' | translate }}</h1> <h1><core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" /></h1>
</ion-title> </ion-title>
<ion-buttons slot="end">
<ion-button fill="clear" (click)="cancel()" [attr.aria-label]="'core.cancel' | translate">
<ion-icon name="fas-xmark" slot="icon-only" aria-hidden=true />
</ion-button>
</ion-buttons>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content class="limited-width">
<core-loading [hideUntil]="policyLoaded"> <core-loading [hideUntil]="policyLoaded">
<ion-list *ngIf="sitePolicy"> <form *ngIf="policyForm" [formGroup]="policyForm" (ngSubmit)="submitAcceptances($event)">
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<p>{{ 'core.policy.policyagree' | translate }}</p> <h2>{{ 'core.policy.policyagreement' | translate }}</h2>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap"> <hr>
<ion-label> <ng-container *ngIf="isPoliciesURL && sitePoliciesURL">
<p> <ion-card class="core-info-card">
<a [href]="sitePolicy" core-link [capture]="false">{{ 'core.policy.policyagreementclick' | translate }}</a> <ion-item class="ion-text-wrap">
</p> <ion-icon name="fas-circle-info" slot="start" aria-hidden="true" />
</ion-label> <ion-label>
</ion-item> <p>{{ 'core.policy.policyagree' | translate }}</p>
<ion-card *ngIf="showInline" class="core-site-policy-iframe-container"> </ion-label>
<core-iframe [src]="sitePolicy" /> </ion-item>
</ion-card> </ion-card>
<ion-button class="ion-text-wrap ion-margin-horizontal" expand="block" (click)="accept()"> <ion-item class="ion-text-wrap core-site-policy-link">
{{ 'core.policy.policyacceptmandatory' | translate }} <ion-label>
</ion-button> <p>
<ion-button class="ion-text-wrap ion-margin-horizontal ion-margin-bottom" expand="block" fill="outline" (click)="cancel()"> <a [href]="sitePoliciesURL" core-link [capture]="false">
{{ 'core.cancel' | translate }} {{ 'core.policy.policyagreementclick' | translate }}<ion-icon name="fas-up-right-from-square"
</ion-button> aria-hidden="true" />
</ion-list> </a>
</p>
</ion-label>
</ion-item>
<div class="core-site-policy-iframe-container" [class.core-policy-has-iframe]="showInline">
<core-iframe *ngIf="showInline" [src]="sitePoliciesURL" />
</div>
<ion-item class="ion-text-wrap">
<ion-checkbox name="agreepolicy" formControlName="agreepolicy">
<span *ngIf="isManageAcceptancesAvailable" [core-mark-required]="true">
{{ 'core.policy.havereadandagreepolicy' | translate:{ policyname:'core.policy.policyagreement' | translate } }}
</span>
<span *ngIf="!isManageAcceptancesAvailable" [core-mark-required]="true">
{{ 'core.policy.policyacceptmandatory' | translate }}
</span>
</ion-checkbox>
</ion-item>
<ion-button type="submit" class="ion-text-wrap ion-margin-horizontal" expand="block" [disabled]="!policyForm.valid">
{{ 'core.continue' | translate }}
</ion-button>
</ng-container>
</form>
</core-loading> </core-loading>
</ion-content> </ion-content>

View File

@ -1,20 +1,45 @@
@use "theme/globals" as *;
:host { :host {
ion-list { hr {
background: var(--black);
margin: 0 16px;
}
form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-grow: 1;
height: 100%; height: 100%;
ion-item { ion-item, .core-info-card {
flex-shrink: 0; flex-shrink: 0;
} }
.core-site-policy-iframe-container { ion-item {
height: 100%; --inner-border-width: 0;
}
core-iframe { .core-site-policy-link {
height: 100%; p {
width: 100%; text-decoration: underline;
font-size: 1rem;
ion-icon {
font-size: 0.875rem;
@include margin-horizontal(4px, 0);
}
} }
} }
.core-site-policy-iframe-container {
margin: 8px;
display: flex;
flex-grow: 1;
}
ion-button[type="submit"] {
margin-bottom: 12px;;
}
} }
} }

View File

@ -24,6 +24,7 @@ import { CoreEvents } from '@singletons/events';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CorePolicy } from '@features/policy/services/policy'; import { CorePolicy } from '@features/policy/services/policy';
import { FormControl, FormGroup, Validators } from '@angular/forms';
/** /**
* Page to accept a site policy. * Page to accept a site policy.
@ -35,20 +36,26 @@ import { CorePolicy } from '@features/policy/services/policy';
}) })
export class CorePolicySitePolicyPage implements OnInit { export class CorePolicySitePolicyPage implements OnInit {
sitePolicy?: string; siteName?: string;
isManageAcceptancesAvailable = false;
isPoliciesURL = false;
sitePoliciesURL?: string;
showInline?: boolean; showInline?: boolean;
policyLoaded?: boolean; policyLoaded?: boolean;
policyForm?: FormGroup;
protected siteId?: string; protected siteId?: string;
protected currentSite!: CoreSite; protected currentSite!: CoreSite;
/** /**
* @inheritdoc * @inheritdoc
*/ */
ngOnInit(): void { async ngOnInit(): Promise<void> {
this.siteId = CoreNavigator.getRouteParam('siteId'); this.siteId = CoreNavigator.getRouteParam('siteId');
try { try {
this.currentSite = CoreSites.getRequiredCurrentSite(); this.currentSite = CoreSites.getRequiredCurrentSite();
this.siteName = (await CoreUtils.ignoreErrors(this.currentSite.getSiteName(), '')) || '';
} catch { } catch {
// Not logged in, stop. // Not logged in, stop.
this.cancel(); this.cancel();
@ -66,17 +73,36 @@ export class CorePolicySitePolicyPage implements OnInit {
return; return;
} }
this.fetchSitePolicy(); this.isManageAcceptancesAvailable = await CorePolicy.isManageAcceptancesAvailable(this.siteId);
this.isPoliciesURL = this.isManageAcceptancesAvailable ?
(await this.currentSite.getConfig('sitepolicyhandler')) !== 'tool_policy' :
true; // Site doesn't support managing acceptances, just display it as a URL.
if (this.isPoliciesURL) {
this.initFormForPoliciesURL();
await this.fetchSitePoliciesURL();
} else {
// TODO
}
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.VIEW_ITEM,
ws: 'auth_email_get_signup_settings',
name: Translate.instant('core.policy.policyagreement'),
data: { category: 'policy' },
url: '/user/policy.php',
});
} }
/** /**
* Fetch the site policy URL. * Fetch the site policies URL.
* *
* @returns Promise resolved when done. * @returns Promise resolved when done.
*/ */
protected async fetchSitePolicy(): Promise<void> { protected async fetchSitePoliciesURL(): Promise<void> {
try { try {
this.sitePolicy = await CorePolicy.getSitePoliciesURL(this.siteId); this.sitePoliciesURL = await CorePolicy.getSitePoliciesURL(this.siteId);
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting site policy.'); CoreDomUtils.showErrorModalDefault(error, 'Error getting site policy.');
this.cancel(); this.cancel();
@ -86,9 +112,9 @@ export class CorePolicySitePolicyPage implements OnInit {
// Try to get the mime type. // Try to get the mime type.
try { try {
const mimeType = await CoreUtils.getMimeTypeFromUrl(this.sitePolicy); const mimeType = await CoreUtils.getMimeTypeFromUrl(this.sitePoliciesURL);
const extension = CoreMimetypeUtils.getExtension(mimeType, this.sitePolicy); const extension = CoreMimetypeUtils.getExtension(mimeType, this.sitePoliciesURL);
this.showInline = extension == 'html' || extension == 'htm'; this.showInline = extension == 'html' || extension == 'htm';
} catch { } catch {
// Unable to get mime type, assume it's not supported. // Unable to get mime type, assume it's not supported.
@ -96,13 +122,17 @@ export class CorePolicySitePolicyPage implements OnInit {
} finally { } finally {
this.policyLoaded = true; this.policyLoaded = true;
} }
}
CoreAnalytics.logEvent({ /**
type: CoreAnalyticsEventType.VIEW_ITEM, * Init the form to accept the policies using a URL.
ws: 'auth_email_get_signup_settings', */
name: Translate.instant('core.policy.policyagreement'), protected initFormForPoliciesURL(): void {
data: { category: 'policy' }, this.policyForm = new FormGroup({
url: '/user/policy.php', agreepolicy: new FormControl(false, {
validators: Validators.requiredTrue,
nonNullable: true,
}),
}); });
} }
@ -118,11 +148,19 @@ export class CorePolicySitePolicyPage implements OnInit {
} }
/** /**
* Accept the site policy. * Submit the acceptances to one or several policies.
* *
* @param event Event.
* @returns Promise resolved when done. * @returns Promise resolved when done.
*/ */
async accept(): Promise<void> { async submitAcceptances(event: Event): Promise<void> {
event.preventDefault();
event.stopPropagation();
if (!this.policyForm?.valid) {
return;
}
const modal = await CoreDomUtils.showModalLoading('core.sending', true); const modal = await CoreDomUtils.showModalLoading('core.sending', true);
try { try {

View File

@ -99,7 +99,9 @@ ion-item .in-item {
// Correctly inherit ion-text-wrap onto labels. // Correctly inherit ion-text-wrap onto labels.
.item > ion-label, .item > ion-label,
.fake-ion-item { .fake-ion-item,
.item.ion-text-wrap > ion-checkbox::part(label),
ion-checkbox.ion-text-wrap::part(label) {
core-format-text, core-format-text,
core-format-text > *:not(pre) { core-format-text > *:not(pre) {
white-space: nowrap; white-space: nowrap;
@ -110,7 +112,9 @@ ion-item .in-item {
.item.ion-text-wrap > ion-label, .item.ion-text-wrap > ion-label,
ion-item > .in-item, ion-item > .in-item,
.fake-ion-item.ion-text-wrap { .fake-ion-item.ion-text-wrap,
.item.ion-text-wrap > ion-checkbox::part(label),
ion-checkbox.ion-text-wrap::part(label) {
core-format-text, core-format-text,
core-format-text > *:not(pre) { core-format-text > *:not(pre) {
white-space: normal; white-space: normal;
@ -118,7 +122,9 @@ ion-item > .in-item,
} }
} }
.item.ion-text-wrap > ion-label { .item.ion-text-wrap > ion-label,
.item.ion-text-wrap > ion-checkbox::part(label),
ion-checkbox.ion-text-wrap::part(label) {
white-space: normal !important; white-space: normal !important;
} }