MOBILE-3759 a11y: Fix a11y of ion-badges

main
Dani Palou 2021-05-14 12:44:58 +02:00
parent f09a46ac16
commit 55b270f6b5
28 changed files with 138 additions and 25 deletions

View File

@ -379,10 +379,15 @@
"addon.mod_assign.noteam_desc": "assign", "addon.mod_assign.noteam_desc": "assign",
"addon.mod_assign.notgraded": "assign", "addon.mod_assign.notgraded": "assign",
"addon.mod_assign.numberofdraftsubmissions": "assign", "addon.mod_assign.numberofdraftsubmissions": "assign",
"addon.mod_assign.numberofdraftsubmissionscountdescription": "local_moodlemobileapp",
"addon.mod_assign.numberofparticipants": "assign", "addon.mod_assign.numberofparticipants": "assign",
"addon.mod_assign.numberofparticipantscountdescription": "local_moodlemobileapp",
"addon.mod_assign.numberofsubmissionsneedgrading": "assign", "addon.mod_assign.numberofsubmissionsneedgrading": "assign",
"addon.mod_assign.numberofsubmissionsneedgradingcountdescription": "local_moodlemobileapp",
"addon.mod_assign.numberofsubmittedassignments": "assign", "addon.mod_assign.numberofsubmittedassignments": "assign",
"addon.mod_assign.numberofsubmittedassignmentscountdescription": "local_moodlemobileapp",
"addon.mod_assign.numberofteams": "assign", "addon.mod_assign.numberofteams": "assign",
"addon.mod_assign.numberofteamscountdescription": "local_moodlemobileapp",
"addon.mod_assign.numwords": "moodle", "addon.mod_assign.numwords": "moodle",
"addon.mod_assign.outof": "assign", "addon.mod_assign.outof": "assign",
"addon.mod_assign.overdue": "assign", "addon.mod_assign.overdue": "assign",
@ -535,6 +540,7 @@
"addon.mod_feedback.captchaofflinewarning": "local_moodlemobileapp", "addon.mod_feedback.captchaofflinewarning": "local_moodlemobileapp",
"addon.mod_feedback.complete_the_form": "feedback", "addon.mod_feedback.complete_the_form": "feedback",
"addon.mod_feedback.completed_feedbacks": "feedback", "addon.mod_feedback.completed_feedbacks": "feedback",
"addon.mod_feedback.completedfeedbackscountdescription": "local_moodlemobileapp",
"addon.mod_feedback.continue_the_form": "feedback", "addon.mod_feedback.continue_the_form": "feedback",
"addon.mod_feedback.feedback_is_not_open": "feedback", "addon.mod_feedback.feedback_is_not_open": "feedback",
"addon.mod_feedback.feedback_submitted_offline": "local_moodlemobileapp", "addon.mod_feedback.feedback_submitted_offline": "local_moodlemobileapp",
@ -557,6 +563,7 @@
"addon.mod_feedback.preview": "moodle", "addon.mod_feedback.preview": "moodle",
"addon.mod_feedback.previous_page": "feedback", "addon.mod_feedback.previous_page": "feedback",
"addon.mod_feedback.questions": "feedback", "addon.mod_feedback.questions": "feedback",
"addon.mod_feedback.questionscountdescription": "local_moodlemobileapp",
"addon.mod_feedback.response_nr": "feedback", "addon.mod_feedback.response_nr": "feedback",
"addon.mod_feedback.responses": "feedback", "addon.mod_feedback.responses": "feedback",
"addon.mod_feedback.save_entries": "feedback", "addon.mod_feedback.save_entries": "feedback",
@ -1472,6 +1479,8 @@
"core.course.couldnotloadsections": "local_moodlemobileapp", "core.course.couldnotloadsections": "local_moodlemobileapp",
"core.course.coursesummary": "moodle", "core.course.coursesummary": "moodle",
"core.course.downloadcourse": "tool_mobile", "core.course.downloadcourse": "tool_mobile",
"core.course.downloadcoursesprogressdescription": "local_moodlemobileapp",
"core.course.downloadsectionprogressdescription": "local_moodlemobileapp",
"core.course.errordownloadingcourse": "local_moodlemobileapp", "core.course.errordownloadingcourse": "local_moodlemobileapp",
"core.course.errordownloadingsection": "local_moodlemobileapp", "core.course.errordownloadingsection": "local_moodlemobileapp",
"core.course.errorgetmodule": "local_moodlemobileapp", "core.course.errorgetmodule": "local_moodlemobileapp",
@ -1531,6 +1540,7 @@
"core.courses.selfenrolment": "local_moodlemobileapp", "core.courses.selfenrolment": "local_moodlemobileapp",
"core.courses.sendpaymentbutton": "enrol_paypal", "core.courses.sendpaymentbutton": "enrol_paypal",
"core.courses.show": "block_myoverview", "core.courses.show": "block_myoverview",
"core.courses.therearecourses": "moodle",
"core.courses.totalcoursesearchresults": "local_moodlemobileapp", "core.courses.totalcoursesearchresults": "local_moodlemobileapp",
"core.currentdevice": "local_moodlemobileapp", "core.currentdevice": "local_moodlemobileapp",
"core.datastoredoffline": "local_moodlemobileapp", "core.datastoredoffline": "local_moodlemobileapp",
@ -1880,6 +1890,7 @@
"core.login.signuprequiredfieldnotsupported": "local_moodlemobileapp", "core.login.signuprequiredfieldnotsupported": "local_moodlemobileapp",
"core.login.siteaddress": "local_moodlemobileapp", "core.login.siteaddress": "local_moodlemobileapp",
"core.login.siteaddressplaceholder": "donottranslate", "core.login.siteaddressplaceholder": "donottranslate",
"core.login.sitebadgedescription": "local_moodlemobileapp",
"core.login.sitehasredirect": "local_moodlemobileapp", "core.login.sitehasredirect": "local_moodlemobileapp",
"core.login.siteinmaintenance": "local_moodlemobileapp", "core.login.siteinmaintenance": "local_moodlemobileapp",
"core.login.sitepolicynotagreederror": "local_moodlemobileapp", "core.login.sitepolicynotagreederror": "local_moodlemobileapp",
@ -2169,6 +2180,7 @@
"core.tag.tagarea_course_modules": "tag", "core.tag.tagarea_course_modules": "tag",
"core.tag.tagarea_post": "tag", "core.tag.tagarea_post": "tag",
"core.tag.tagarea_user": "tag", "core.tag.tagarea_user": "tag",
"core.tag.tagareabadgedescription": "local_moodlemobileapp",
"core.tag.tags": "moodle", "core.tag.tags": "moodle",
"core.tag.warningareasnotsupported": "local_moodlemobileapp", "core.tag.warningareasnotsupported": "local_moodlemobileapp",
"core.teachers": "moodle", "core.teachers": "moodle",

View File

@ -10,10 +10,13 @@
<ion-icon [name]="prefetchCoursesData[selectedFilter].icon" slot="icon-only" aria-hidden="true"> <ion-icon [name]="prefetchCoursesData[selectedFilter].icon" slot="icon-only" aria-hidden="true">
</ion-icon> </ion-icon>
</ion-button> </ion-button>
<ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData[selectedFilter].badge"> <ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData[selectedFilter].badge"
role="progressbar" aria-valuemin="0" [attr.aria-valuemax]="prefetchCoursesData[selectedFilter].total"
[attr.aria-valuenow]="prefetchCoursesData[selectedFilter].count"
[attr.aria-valuetext]="prefetchCoursesData[selectedFilter].badgeA11yText">
{{prefetchCoursesData[selectedFilter].badge}} {{prefetchCoursesData[selectedFilter].badge}}
</ion-badge> </ion-badge>
<ion-spinner *ngIf="prefetchCoursesData[selectedFilter].loading"></ion-spinner> <ion-spinner *ngIf="prefetchCoursesData[selectedFilter].loading" aria-hidden="true"></ion-spinner>
</div> </div>
<core-context-menu slot="end"> <core-context-menu slot="end">
<core-context-menu-item *ngIf="loaded && showFilterSwitchButton()" [priority]="1000" <core-context-menu-item *ngIf="loaded && showFilterSwitchButton()" [priority]="1000"

View File

@ -7,7 +7,9 @@
(click)="prefetchCourses()" [attr.aria-label]="'core.courses.downloadcourses' | translate"> (click)="prefetchCourses()" [attr.aria-label]="'core.courses.downloadcourses' | translate">
<ion-icon [name]="prefetchCoursesData.icon" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon [name]="prefetchCoursesData.icon" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button> </ion-button>
<ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData.badge"> <ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData.badge"
role="progressbar" aria-valuemin="0" [attr.aria-valuemax]="prefetchCoursesData.total"
[attr.aria-valuenow]="prefetchCoursesData.count" [attr.aria-valuetext]="prefetchCoursesData.badgeA11yText">
{{prefetchCoursesData.badge}} {{prefetchCoursesData.badge}}
</ion-badge> </ion-badge>
<ion-spinner *ngIf="!prefetchCoursesData.icon || prefetchCoursesData.loading"></ion-spinner> <ion-spinner *ngIf="!prefetchCoursesData.icon || prefetchCoursesData.loading"></ion-spinner>

View File

@ -7,7 +7,9 @@
(click)="prefetchCourses()" [attr.aria-label]="'core.courses.downloadcourses' | translate"> (click)="prefetchCourses()" [attr.aria-label]="'core.courses.downloadcourses' | translate">
<ion-icon [name]="prefetchCoursesData.icon" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon [name]="prefetchCoursesData.icon" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button> </ion-button>
<ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData.badge"> <ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData.badge"
role="progressbar" aria-valuemin="0" [attr.aria-valuemax]="prefetchCoursesData.total"
[attr.aria-valuenow]="prefetchCoursesData.count" [attr.aria-valuetext]="prefetchCoursesData.badgeA11yText">
{{prefetchCoursesData.badge}} {{prefetchCoursesData.badge}}
</ion-badge> </ion-badge>
<ion-spinner *ngIf="!prefetchCoursesData.icon || prefetchCoursesData.loading"></ion-spinner> <ion-spinner *ngIf="!prefetchCoursesData.icon || prefetchCoursesData.loading"></ion-spinner>

View File

@ -56,7 +56,8 @@
</core-tab> </core-tab>
<!-- Requests tab. --> <!-- Requests tab. -->
<core-tab [title]="'addon.messages.requests' | translate" (ionSelect)="selectTab('requests')" [badge]="requestsBadge"> <core-tab [title]="'addon.messages.requests' | translate" (ionSelect)="selectTab('requests')" [badge]="requestsBadge"
badgeA11yText="addon.messages.pendingcontactrequests">
<ng-template> <ng-template>
<ion-refresher slot="fixed" [disabled]="!requestsLoaded" (ionRefresh)="refreshData($event.target)"> <ion-refresher slot="fixed" [disabled]="!requestsLoaded" (ionRefresh)="refreshData($event.target)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>

View File

@ -96,7 +96,13 @@
<h2 *ngIf="!assign.teamsubmission">{{ 'addon.mod_assign.numberofparticipants' | translate }}</h2> <h2 *ngIf="!assign.teamsubmission">{{ 'addon.mod_assign.numberofparticipants' | translate }}</h2>
</ion-label> </ion-label>
<ion-badge slot="end" *ngIf="showNumbers" color="primary"> <ion-badge slot="end" *ngIf="showNumbers" color="primary">
{{ summary.participantcount }} <span aria-hidden="true">{{ summary.participantcount }}</span>
<span class="sr-only" *ngIf="!assign.teamsubmission">
{{ 'addon.mod_assign.numberofparticipantscountdescription' | translate:{count: summary.participantcount} }}
</span>
<span class="sr-only" *ngIf="assign.teamsubmission">
{{ 'addon.mod_assign.numberofteamscountdescription' | translate:{count: summary.participantcount} }}
</span>
</ion-badge> </ion-badge>
</ion-item> </ion-item>
@ -107,7 +113,11 @@
(click)="goToSubmissionList(submissionStatusDraft, !!summary.submissiondraftscount)"> (click)="goToSubmissionList(submissionStatusDraft, !!summary.submissiondraftscount)">
<ion-label><h2>{{ 'addon.mod_assign.numberofdraftsubmissions' | translate }}</h2></ion-label> <ion-label><h2>{{ 'addon.mod_assign.numberofdraftsubmissions' | translate }}</h2></ion-label>
<ion-badge slot="end" *ngIf="showNumbers" color="primary"> <ion-badge slot="end" *ngIf="showNumbers" color="primary">
{{ summary.submissiondraftscount }} <span aria-hidden="true">{{ summary.submissiondraftscount }}</span>
<span class="sr-only">
{{ 'addon.mod_assign.numberofdraftsubmissionscountdescription' | translate:
{count: summary.submissiondraftscount} }}
</span>
</ion-badge> </ion-badge>
</ion-item> </ion-item>
@ -118,7 +128,11 @@
(click)="goToSubmissionList(submissionStatusSubmitted, !!summary.submissionssubmittedcount)"> (click)="goToSubmissionList(submissionStatusSubmitted, !!summary.submissionssubmittedcount)">
<ion-label><h2>{{ 'addon.mod_assign.numberofsubmittedassignments' | translate }}</h2></ion-label> <ion-label><h2>{{ 'addon.mod_assign.numberofsubmittedassignments' | translate }}</h2></ion-label>
<ion-badge slot="end" *ngIf="showNumbers" color="primary"> <ion-badge slot="end" *ngIf="showNumbers" color="primary">
{{ summary.submissionssubmittedcount }} <span aria-hidden="true">{{ summary.submissionssubmittedcount }}</span>
<span class="sr-only">
{{ 'addon.mod_assign.numberofsubmittedassignmentscountdescription' | translate:
{count: summary.submissionssubmittedcount} }}
</span>
</ion-badge> </ion-badge>
</ion-item> </ion-item>
@ -128,7 +142,11 @@
(click)="goToSubmissionList(needGrading, needsGradingAvailable)"> (click)="goToSubmissionList(needGrading, needsGradingAvailable)">
<ion-label><h2>{{ 'addon.mod_assign.numberofsubmissionsneedgrading' | translate }}</h2></ion-label> <ion-label><h2>{{ 'addon.mod_assign.numberofsubmissionsneedgrading' | translate }}</h2></ion-label>
<ion-badge slot="end" color="primary"> <ion-badge slot="end" color="primary">
{{ summary.submissionsneedgradingcount }} <span aria-hidden="true">{{ summary.submissionsneedgradingcount }}</span>
<span class="sr-only">
{{ 'addon.mod_assign.numberofsubmissionsneedgradingcountdescription' | translate:
{count: summary.submissionsneedgradingcount} }}
</span>
</ion-badge> </ion-badge>
</ion-item> </ion-item>
</ion-list> </ion-list>

View File

@ -64,10 +64,15 @@
"noteam_desc": "This assignment requires submission in groups. You are not a member of any group, so you cannot create a submission. Please contact your teacher to be added to a group.", "noteam_desc": "This assignment requires submission in groups. You are not a member of any group, so you cannot create a submission. Please contact your teacher to be added to a group.",
"notgraded": "Not graded", "notgraded": "Not graded",
"numberofdraftsubmissions": "Drafts", "numberofdraftsubmissions": "Drafts",
"numberofdraftsubmissionscountdescription": "There are {{count}} drafts.",
"numberofparticipants": "Participants", "numberofparticipants": "Participants",
"numberofparticipantscountdescription": "There are {{count}} participants.",
"numberofsubmittedassignments": "Submitted", "numberofsubmittedassignments": "Submitted",
"numberofsubmittedassignmentscountdescription": "There are {{count}} submitted assignments.",
"numberofsubmissionsneedgrading": "Needs grading", "numberofsubmissionsneedgrading": "Needs grading",
"numberofsubmissionsneedgradingcountdescription": "There are {{count}} submissions that need grading.",
"numberofteams": "Groups", "numberofteams": "Groups",
"numberofteamscountdescription": "There are {{count}} teams.",
"numwords": "{{$a}} words", "numwords": "{{$a}} words",
"outof": "{{$a.current}} out of {{$a.total}}", "outof": "{{$a.current}} out of {{$a.total}}",
"overdue": "<font color=\"red\">Assignment is overdue by: {{$a}}</font>", "overdue": "<font color=\"red\">Assignment is overdue by: {{$a}}</font>",

View File

@ -78,7 +78,12 @@
<ion-label> <ion-label>
<h2>{{ 'addon.mod_feedback.completed_feedbacks' | translate }}</h2> <h2>{{ 'addon.mod_feedback.completed_feedbacks' | translate }}</h2>
</ion-label> </ion-label>
<ion-badge slot="end">{{completedCount}}</ion-badge> <ion-badge slot="end">
<span aria-hidden="true">{{completedCount}}</span>
<span class="sr-only">
{{ 'addon.mod_feedback.completedfeedbackscountdescription' | translate:{count: completedCount} }}
</span>
</ion-badge>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="!access.isanonymous && access.canviewreports" (click)="openNonRespondents()" <ion-item class="ion-text-wrap" *ngIf="!access.isanonymous && access.canviewreports" (click)="openNonRespondents()"
detail="true" button> detail="true" button>
@ -90,7 +95,12 @@
<ion-label> <ion-label>
<h2>{{ 'addon.mod_feedback.questions' | translate }}</h2> <h2>{{ 'addon.mod_feedback.questions' | translate }}</h2>
</ion-label> </ion-label>
<ion-badge slot="end">{{itemsCount}}</ion-badge> <ion-badge slot="end">
<span aria-hidden="true">{{itemsCount}}</span>
<span class="sr-only">
{{ 'addon.mod_feedback.questionscountdescription' | translate:{count: itemsCount} }}
</span>
</ion-badge>
</ion-item> </ion-item>
</ion-list> </ion-list>
</ng-template> </ng-template>

View File

@ -6,6 +6,7 @@
"captchaofflinewarning": "Feedback with CAPTCHA cannot be completed offline, or if not configured, or if the server is down.", "captchaofflinewarning": "Feedback with CAPTCHA cannot be completed offline, or if not configured, or if the server is down.",
"complete_the_form": "Answer the questions", "complete_the_form": "Answer the questions",
"completed_feedbacks": "Submitted answers", "completed_feedbacks": "Submitted answers",
"completedfeedbackscountdescription": "There are {{count}} submitted answers.",
"continue_the_form": "Continue answering the questions", "continue_the_form": "Continue answering the questions",
"feedback_is_not_open": "The feedback is not open", "feedback_is_not_open": "The feedback is not open",
"feedback_submitted_offline": "This feedback has been saved to be submitted later.", "feedback_submitted_offline": "This feedback has been saved to be submitted later.",
@ -28,6 +29,7 @@
"preview": "Preview", "preview": "Preview",
"previous_page": "Previous page", "previous_page": "Previous page",
"questions": "Questions", "questions": "Questions",
"questionscountdescription": "There are {{count}} questions.",
"response_nr": "Response number", "response_nr": "Response number",
"responses": "Responses", "responses": "Responses",
"save_entries": "Submit your answers", "save_entries": "Submit your answers",

View File

@ -142,9 +142,11 @@
<ion-note> <ion-note>
<ion-icon name="fas-comments" aria-hidden="true"></ion-icon> <ion-icon name="fas-comments" aria-hidden="true"></ion-icon>
{{ 'addon.mod_forum.numreplies' | translate:{numreplies: discussion.numreplies} }} {{ 'addon.mod_forum.numreplies' | translate:{numreplies: discussion.numreplies} }}
<ion-badge *ngIf="discussion.numunread" class="ion-text-center" <ion-badge *ngIf="discussion.numunread" class="ion-text-center">
[attr.aria-label]="'addon.mod_forum.unreadpostsnumber' | translate:{ '$a' : discussion.numunread}"> <span aria-hidden="true">{{ discussion.numunread }}</span>
{{ discussion.numunread }} <span class="sr-only">
{{ 'addon.mod_forum.unreadpostsnumber' | translate:{ '$a' : discussion.numunread} }}
</span>
</ion-badge> </ion-badge>
</ion-note> </ion-note>
</ion-col> </ion-col>

View File

@ -670,5 +670,6 @@ export type CoreTabBase = {
icon?: string; // The tab icon. icon?: string; // The tab icon.
badge?: string; // A badge to add in the tab. badge?: string; // A badge to add in the tab.
badgeStyle?: string; // The badge color. badgeStyle?: string; // The badge color.
badgeA11yText?: string; // Accessibility text to add on the badge.
enabled?: boolean; // Whether the tab is enabled. enabled?: boolean; // Whether the tab is enabled.
}; };

View File

@ -50,6 +50,7 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange
@Input() priority?: number; // Used to sort items. The highest priority, the highest position. @Input() priority?: number; // Used to sort items. The highest priority, the highest position.
@Input() badge?: string; // A badge to show in the item. @Input() badge?: string; // A badge to show in the item.
@Input() badgeClass?: number; // A class to set in the badge. @Input() badgeClass?: number; // A class to set in the badge.
@Input() badgeA11yText?: string; // Description for the badge, if needed.
@Input() hidden?: boolean; // Whether the item should be hidden. @Input() hidden?: boolean; // Whether the item should be hidden.
@Output() action?: EventEmitter<() => void>; // Will emit an event when the item clicked. @Output() action?: EventEmitter<() => void>; // Will emit an event when the item clicked.
@Output() onClosed?: EventEmitter<() => void>; // Will emit an event when the popover is closed because the item was clicked. @Output() onClosed?: EventEmitter<() => void>; // Will emit an event when the popover is closed because the item was clicked.

View File

@ -12,6 +12,11 @@
[class.icon-slash]="item.iconSlash" slot="end"> [class.icon-slash]="item.iconSlash" slot="end">
</ion-icon> </ion-icon>
<ion-spinner *ngIf="(item.href || item.action) && item.iconAction == 'spinner'" slot="end"></ion-spinner> <ion-spinner *ngIf="(item.href || item.action) && item.iconAction == 'spinner'" slot="end"></ion-spinner>
<ion-badge class="{{item.badgeClass}}" slot="end" *ngIf="item.badge">{{item.badge}}</ion-badge> <ion-badge class="{{item.badgeClass}}" slot="end" *ngIf="item.badge">
<span [attr.ara-hidden]="!!item.badgeA11yText">{{item.badge}}</span>
<span class="sr-only" *ngIf="item.badgeA11yText">
{{ item.badgeA11yText | translate: {$a : item.badge } }}
</span>
</ion-badge>
</ion-item> </ion-item>
</ion-list> </ion-list>

View File

@ -30,7 +30,12 @@
> >
<ion-icon *ngIf="tab.icon" [name]="tab.icon" aria-hidden="true"></ion-icon> <ion-icon *ngIf="tab.icon" [name]="tab.icon" aria-hidden="true"></ion-icon>
<ion-label>{{ tab.title | translate}}</ion-label> <ion-label>{{ tab.title | translate}}</ion-label>
<ion-badge *ngIf="tab.badge">{{ tab.badge }}</ion-badge> <ion-badge *ngIf="tab.badge">
<span [attr.aria-hidden]="!!tab.badgeA11yText">{{ tab.badge }}</span>
<span *ngIf="tab.badgeA11yText" class="sr-only">
{{ tab.badgeA11yText | translate: {$a : tab.badge } }}
</span>
</ion-badge>
</ion-tab-button> </ion-tab-button>
</ion-slide> </ion-slide>
</ng-container> </ng-container>

View File

@ -28,7 +28,12 @@
> >
<ion-icon *ngIf="tab.icon" [name]="tab.icon" aria-hidden="true"></ion-icon> <ion-icon *ngIf="tab.icon" [name]="tab.icon" aria-hidden="true"></ion-icon>
<ion-label>{{ tab.title | translate}}</ion-label> <ion-label>{{ tab.title | translate}}</ion-label>
<ion-badge *ngIf="tab.badge">{{ tab.badge }}</ion-badge> <ion-badge *ngIf="tab.badge">
<span [attr.aria-hidden]="!!tab.badgeA11yText">{{ tab.badge }}</span>
<span *ngIf="tab.badgeA11yText" class="sr-only">
{{ tab.badgeA11yText | translate: {$a : tab.badge } }}
</span>
</ion-badge>
</ion-tab-button> </ion-tab-button>
</ion-slide> </ion-slide>
</ng-container> </ng-container>

View File

@ -49,6 +49,7 @@ export class CoreTabComponent implements OnInit, OnDestroy, CoreTabBase {
@Input() icon?: string; // The tab icon. @Input() icon?: string; // The tab icon.
@Input() badge?: string; // A badge to add in the tab. @Input() badge?: string; // A badge to add in the tab.
@Input() badgeStyle?: string; // The badge color. @Input() badgeStyle?: string; // The badge color.
@Input() badgeA11yText?: string; // Accessibility text to add on the badge.
@Input() class?: string; // Class, if needed. @Input() class?: string; // Class, if needed.
@Input() set enabled(value: boolean) { // Whether the tab should be shown. @Input() set enabled(value: boolean) { // Whether the tab should be shown.
value = value === undefined ? true : value; value = value === undefined ? true : value;

View File

@ -158,7 +158,9 @@
<div *ngIf="section && downloadEnabled" slot="end" class="core-button-spinner core-section-download"> <div *ngIf="section && downloadEnabled" slot="end" class="core-button-spinner core-section-download">
<!-- Download progress. --> <!-- Download progress. -->
<ion-badge class="core-course-download-section-progress" <ion-badge class="core-course-download-section-progress"
*ngIf="section.isDownloading && section.total > 0 && section.count < section.total"> *ngIf="section.isDownloading && section.total > 0 && section.count < section.total" role="progressbar"
aria-valuemin="0" [attr.aria-valuemax]="section.total" [attr.aria-valuenow]="section.count"
[attr.aria-valuetext]="'core.course.downloadsectionprogressdescription' | translate:section">
{{section.count}} / {{section.total}} {{section.count}} / {{section.total}}
</ion-badge> </ion-badge>

View File

@ -30,6 +30,8 @@
"couldnotloadsections": "Could not load the sections. Please try again later.", "couldnotloadsections": "Could not load the sections. Please try again later.",
"coursesummary": "Course summary", "coursesummary": "Course summary",
"downloadcourse": "Download course", "downloadcourse": "Download course",
"downloadcoursesprogressdescription": "Downloading courses: downloaded {{count}} out of {{total}}.",
"downloadsectionprogressdescription": "Downloading section: downloaded {{count}} out of {{total}}.",
"errordownloadingcourse": "Error downloading course.", "errordownloadingcourse": "Error downloading course.",
"errordownloadingsection": "Error downloading section.", "errordownloadingsection": "Error downloading section.",
"errorgetmodule": "Error getting activity data.", "errorgetmodule": "Error getting activity data.",

View File

@ -135,6 +135,9 @@ export type CorePrefetchStatusInfo = {
icon: string; // Icon based on the status. icon: string; // Icon based on the status.
loading: boolean; // If it's a loading status. loading: boolean; // If it's a loading status.
badge?: string; // Progress badge string if any. badge?: string; // Progress badge string if any.
badgeA11yText?: string; // Description of the badge if any.
count?: number; // Amount of already downloaded courses.
total?: number; // Total of courses.
downloadSucceeded?: boolean; // Whether download has succeeded (in case it's downloaded). downloadSucceeded?: boolean; // Whether download has succeeded (in case it's downloaded).
}; };
@ -1235,6 +1238,9 @@ export class CoreCourseHelperProvider {
try { try {
await this.confirmAndPrefetchCourses(courses, (progress) => { await this.confirmAndPrefetchCourses(courses, (progress) => {
prefetch.badge = progress.count + ' / ' + progress.total; prefetch.badge = progress.count + ' / ' + progress.total;
prefetch.badgeA11yText = Translate.instant('core.course.downloadcoursesprogressdescription', progress);
prefetch.count = progress.count;
prefetch.total = progress.total;
}); });
prefetch.icon = CoreConstants.ICON_OUTDATED; prefetch.icon = CoreConstants.ICON_OUTDATED;
} finally { } finally {

View File

@ -40,5 +40,6 @@
"selfenrolment": "Self enrolment", "selfenrolment": "Self enrolment",
"sendpaymentbutton": "Send payment via PayPal", "sendpaymentbutton": "Send payment via PayPal",
"show": "Restore to view", "show": "Restore to view",
"therearecourses": "There are {{$a}} courses",
"totalcoursesearchresults": "Total courses: {{$a}}" "totalcoursesearchresults": "Total courses: {{$a}}"
} }

View File

@ -48,7 +48,10 @@
</core-format-text> </core-format-text>
</h2> </h2>
</ion-label> </ion-label>
<ion-badge slot="end" *ngIf="category.coursecount > 0" color="light">{{category.coursecount}}</ion-badge> <ion-badge slot="end" *ngIf="category.coursecount > 0" color="light">
<span aria-hidden="true">{{ category.coursecount }}</span>
<span class="sr-only">{{ 'core.courses.therearecourses' | translate:{ $a: category.coursecount } }}</span>
</ion-badge>
</ion-item> </ion-item>
</section> </section>
</div> </div>

View File

@ -20,7 +20,10 @@
[attr.aria-label]="'core.loading' | translate"></ion-spinner> [attr.aria-label]="'core.loading' | translate"></ion-spinner>
<ion-badge [hidden]="!downloadAllCoursesEnabled || !courses || courses.length < 2 || !downloadAllCoursesLoading || <ion-badge [hidden]="!downloadAllCoursesEnabled || !courses || courses.length < 2 || !downloadAllCoursesLoading ||
downloadAllCoursesBadge == '' || !downloadAllCoursesLoading" downloadAllCoursesBadge == '' || !downloadAllCoursesLoading"
[attr.aria-label]="'core.downloading' | translate">{{downloadAllCoursesBadge}}</ion-badge> role="progressbar" aria-valuemin="0" [attr.aria-valuemax]="downloadAllCoursesTotal"
[attr.aria-valuenow]="downloadAllCoursesCount" [attr.aria-valuetext]="downloadAllCoursesBadgeA11yText">
{{downloadAllCoursesBadge}}
</ion-badge>
</core-navbar-buttons> </core-navbar-buttons>
</ion-buttons> </ion-buttons>
</ion-toolbar> </ion-toolbar>

View File

@ -26,6 +26,7 @@ import { CoreCourseHelper } from '@features/course/services/course-helper';
import { CoreConstants } from '@/core/constants'; import { CoreConstants } from '@/core/constants';
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { Translate } from '@singletons';
/** /**
* Page that displays the list of courses the user is enrolled in. * Page that displays the list of courses the user is enrolled in.
@ -48,6 +49,9 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy {
downloadAllCoursesLoading = false; downloadAllCoursesLoading = false;
downloadAllCoursesBadge = ''; downloadAllCoursesBadge = '';
downloadAllCoursesEnabled = false; downloadAllCoursesEnabled = false;
downloadAllCoursesCount?: number;
downloadAllCoursesTotal?: number;
downloadAllCoursesBadgeA11yText = '';
protected myCoursesObserver: CoreEventObserver; protected myCoursesObserver: CoreEventObserver;
protected siteUpdatedObserver: CoreEventObserver; protected siteUpdatedObserver: CoreEventObserver;
@ -183,6 +187,10 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy {
try { try {
await CoreCourseHelper.confirmAndPrefetchCourses(this.courses, (progress) => { await CoreCourseHelper.confirmAndPrefetchCourses(this.courses, (progress) => {
this.downloadAllCoursesBadge = progress.count + ' / ' + progress.total; this.downloadAllCoursesBadge = progress.count + ' / ' + progress.total;
this.downloadAllCoursesBadgeA11yText =
Translate.instant('core.course.downloadcoursesprogressdescription', progress);
this.downloadAllCoursesCount = progress.count;
this.downloadAllCoursesTotal = progress.total;
}); });
} catch (error) { } catch (error) {
if (!this.isDestroyed) { if (!this.isDestroyed) {

View File

@ -35,7 +35,12 @@
contextLevel="course" contextLevel="course"
></core-format-text> ></core-format-text>
</ion-label> </ion-label>
<ion-badge slot="end" color="light">{{course.grade}}</ion-badge> <ion-badge slot="end" color="light">
<span class="sr-only" *ngIf="course.grade && course.grade != '-'">
{{ 'core.grades.grade' | translate }}
</span>
{{course.grade}}
</ion-badge>
</ion-item> </ion-item>
</ion-list> </ion-list>
</core-loading> </core-loading>

View File

@ -105,6 +105,7 @@
"signuprequiredfieldnotsupported": "The signup form contains a required custom field that isn't supported in the app. Please create your account using a web browser.", "signuprequiredfieldnotsupported": "The signup form contains a required custom field that isn't supported in the app. Please create your account using a web browser.",
"siteaddress": "Your site", "siteaddress": "Your site",
"siteaddressplaceholder": "https://campus.example.edu", "siteaddressplaceholder": "https://campus.example.edu",
"sitebadgedescription": "There are {{count}} unread notifications.",
"sitehasredirect": "Your site contains at least one HTTP redirect. The app cannot follow redirects, this could be the issue that's preventing the app from connecting to your site.", "sitehasredirect": "Your site contains at least one HTTP redirect. The app cannot follow redirects, this could be the issue that's preventing the app from connecting to your site.",
"siteinmaintenance": "Your site is in maintenance mode", "siteinmaintenance": "Your site is in maintenance mode",
"sitepolicynotagreederror": "Site policy not agreed.", "sitepolicynotagreederror": "Site policy not agreed.",

View File

@ -29,7 +29,10 @@
<p><core-format-text [text]="site.siteName" clean="true" [siteId]="site.id"></core-format-text></p> <p><core-format-text [text]="site.siteName" clean="true" [siteId]="site.id"></core-format-text></p>
<p>{{site.siteUrl}}</p> <p>{{site.siteUrl}}</p>
</ion-label> </ion-label>
<ion-badge slot="end" *ngIf="!showDelete && site.badge">{{site.badge}}</ion-badge> <ion-badge slot="end" *ngIf="!showDelete && site.badge">
<span aria-hidden="true">{{site.badge}}</span>
<span class="sr-only">{{ 'core.login.sitebadgedescription' | translate:{ count: site.badge } }}</span>
</ion-badge>
<ion-button *ngIf="showDelete" slot="end" fill="clear" color="danger" (click)="deleteSite($event, site)" <ion-button *ngIf="showDelete" slot="end" fill="clear" color="danger" (click)="deleteSite($event, site)"
[attr.aria-label]="'core.delete' | translate"> [attr.aria-label]="'core.delete' | translate">
<ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon>

View File

@ -8,6 +8,7 @@
"searchtags": "Search tags", "searchtags": "Search tags",
"showingfirsttags": "Showing {{$a}} most popular tags", "showingfirsttags": "Showing {{$a}} most popular tags",
"tag": "Tag", "tag": "Tag",
"tagareabadgedescription": "There are {{count}} items.",
"tagarea_course": "Courses", "tagarea_course": "Courses",
"tagarea_course_modules": "Activities and resources", "tagarea_course_modules": "Activities and resources",
"tagarea_post": "Blog posts", "tagarea_post": "Blog posts",

View File

@ -23,7 +23,10 @@
<ion-label> <ion-label>
<h2>{{ area!.nameKey | translate }}</h2> <h2>{{ area!.nameKey | translate }}</h2>
</ion-label> </ion-label>
<ion-badge slot="end" *ngIf="area!.badge">{{ area!.badge }}</ion-badge> <ion-badge slot="end" *ngIf="area!.badge">
<span aria-hidden="true">{{ area!.badge }}</span>
<span class="sr-only">{{ 'core.tag.tagareabadgedescription' | translate:{ count: area!.badge } }}</span>
</ion-badge>
</ion-item> </ion-item>
</ion-list> </ion-list>
<core-empty-box icon="fa-tag" *ngIf="!hasUnsupportedAreas && (!areas || !areas.length)" <core-empty-box icon="fa-tag" *ngIf="!hasUnsupportedAreas && (!areas || !areas.length)"