Merge pull request #2824 from crazyserver/MOBILE-3320

Mobile 3320
main
Dani Palou 2021-06-11 08:13:43 +02:00 committed by GitHub
commit 7aae777e89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 75 additions and 44 deletions

View File

@ -79,7 +79,7 @@
<!-- Subquestion --> <!-- Subquestion -->
<ion-radio-group [(ngModel)]="answers[question.name]" [required]="question.required" [name]="question.name"> <ion-radio-group [(ngModel)]="answers[question.name]" [required]="question.required" [name]="question.name">
<ion-row *ngIf="question.parent !== 0" class="ion-align-items-center ion-padding" [class.even]="isEven"> <ion-row *ngIf="question.parent !== 0" class="ion-align-items-center ion-padding-horizontal" [class.even]="isEven">
<ion-col size="7"> <ion-col size="7">
<ion-label id="addon-mod_survey-{{question.id}}"> <ion-label id="addon-mod_survey-{{question.id}}">
@ -91,9 +91,10 @@
<!-- Tablet view: radio buttons --> <!-- Tablet view: radio buttons -->
<ion-col class="ion-hide-md-down ion-text-center" size="1" <ion-col class="ion-hide-md-down ion-text-center" size="1"
*ngFor="let option of question.optionsArray; let value=index;"> *ngFor="let option of question.optionsArray; let value=index;"
>
<!-- Empty slot to avoid errors on migration tslint checks --> <!-- Empty slot to avoid errors on migration tslint checks -->
<ion-radio [value]="value + 1" [attr.aria-labelledby]="'addon-mod_survey-'+question.id" slot=""> <ion-radio [value]="value + 1" [attr.aria-label]="question.num + '. '+question.text + ': ' + option">
</ion-radio> </ion-radio>
</ion-col> </ion-col>
<ion-col class="ion-hide-md-up" size="5"> <ion-col class="ion-hide-md-up" size="5">

View File

@ -17,6 +17,12 @@
.even { .even {
background-color: var(--even-background); background-color: var(--even-background);
} }
ion-radio {
height: var(--a11y-min-target-size);
width: var(--a11y-min-target-size);
padding: 12px;
}
} }
:host-context(body.dark) { :host-context(body.dark) {

View File

@ -1,4 +1,4 @@
<div *ngIf="ddQuestion && (ddQuestion.text || ddQuestion.text === '')" class="addon-qtype-ddwtos-container"> <div *ngIf="ddQuestion && (ddQuestion.text || ddQuestion.text === '')">
<!-- Content is outside the core-loading to let the script calculate drag items position --> <!-- Content is outside the core-loading to let the script calculate drag items position -->
<core-loading [hideUntil]="ddQuestion.loaded"></core-loading> <core-loading [hideUntil]="ddQuestion.loaded"></core-loading>
@ -10,16 +10,18 @@
<ion-label>{{ 'core.question.howtodraganddrop' | translate }}</ion-label> <ion-label>{{ 'core.question.howtodraganddrop' | translate }}</ion-label>
</ion-item> </ion-item>
</ion-card> </ion-card>
<core-format-text [component]="component" [componentId]="componentId" [text]="ddQuestion.text" <div class="addon-qtype-ddwtos-container">
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" #questiontext <core-format-text [component]="component" [componentId]="componentId" [text]="ddQuestion.text"
(afterRender)="textRendered()"> [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId" #questiontext
</core-format-text> (afterRender)="textRendered()">
</core-format-text>
<core-format-text *ngIf="ddQuestion.answers" [component]="component" [componentId]="componentId" <core-format-text *ngIf="ddQuestion.answers" [component]="component" [componentId]="componentId"
[text]="ddQuestion.answers" [filter]="false" (afterRender)="answersRendered()"> [text]="ddQuestion.answers" [filter]="false" (afterRender)="answersRendered()">
</core-format-text> </core-format-text>
<div class="drags"></div> <div class="drags"></div>
</div>
</ion-label> </ion-label>
</ion-item> </ion-item>
</div> </div>

View File

@ -3,6 +3,7 @@
// Style ddwtos content a bit. Almost all these styles are copied from Moodle. // Style ddwtos content a bit. Almost all these styles are copied from Moodle.
.addon-qtype-ddwtos-container { .addon-qtype-ddwtos-container {
min-height: 80px; // To display the loading. min-height: 80px; // To display the loading.
position: relative;
} }
core-format-text ::ng-deep, .drags ::ng-deep { core-format-text ::ng-deep, .drags ::ng-deep {

View File

@ -61,7 +61,6 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
numTabsShown = 0; numTabsShown = 0;
direction = 'ltr'; direction = 'ltr';
description = ''; description = '';
lastScroll = 0;
slidesOpts = { slidesOpts = {
initialSlide: 0, initialSlide: 0,
slidesPerView: 3, slidesPerView: 3,
@ -89,6 +88,8 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
protected slidesSwiper: any; // eslint-disable-line @typescript-eslint/no-explicit-any protected slidesSwiper: any; // eslint-disable-line @typescript-eslint/no-explicit-any
protected slidesSwiperLoaded = false; protected slidesSwiperLoaded = false;
protected scrollElements: Record<string | number, HTMLElement> = {}; // Scroll elements for each loaded tab. protected scrollElements: Record<string | number, HTMLElement> = {}; // Scroll elements for each loaded tab.
protected lastScroll = 0;
protected previousLastScroll = 0;
tabAction: CoreTabsRoleTab<T>; tabAction: CoreTabsRoleTab<T>;
@ -164,6 +165,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
this.tabBarElement!.classList.remove('tabs-hidden'); this.tabBarElement!.classList.remove('tabs-hidden');
if (scroll === 0) { if (scroll === 0) {
this.tabBarElement!.style.height = ''; this.tabBarElement!.style.height = '';
this.previousLastScroll = this.lastScroll;
this.lastScroll = 0; this.lastScroll = 0;
} else if (scroll !== undefined) { } else if (scroll !== undefined) {
this.tabBarElement!.style.height = (this.tabBarHeight - scroll) + 'px'; this.tabBarElement!.style.height = (this.tabBarHeight - scroll) + 'px';
@ -253,17 +255,13 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
return; return;
} }
if (!this.tabsShown) { if (window.innerHeight >= CoreTabsBaseComponent.MAX_HEIGHT_TO_HIDE_TABS) {
if (window.innerHeight >= CoreTabsBaseComponent.MAX_HEIGHT_TO_HIDE_TABS) { // Ensure tabbar is shown.
// Ensure tabbar is shown. this.applyScroll(true, 0);
this.tabsShown = true; this.calculateTabBarHeight();
this.tabBarElement?.classList.remove('tabs-hidden'); } else if (!this.tabsShown) {
this.lastScroll = 0; // Don't recalculate.
this.calculateTabBarHeight(); return;
} else {
// Don't recalculate.
return;
}
} }
await this.calculateMaxSlides(); await this.calculateMaxSlides();
@ -504,7 +502,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
return; return;
} }
if (scrollTop == this.lastScroll) { if (scrollTop == this.lastScroll || scrollTop == this.previousLastScroll) {
// Ensure scroll has been modified to avoid flicks. // Ensure scroll has been modified to avoid flicks.
return; return;
} }
@ -522,6 +520,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
} }
// Use lastScroll after moving the tabs to avoid flickering. // Use lastScroll after moving the tabs to avoid flickering.
this.previousLastScroll = this.lastScroll;
this.lastScroll = scrollTop; this.lastScroll = scrollTop;
} }

View File

@ -2,7 +2,8 @@
<ion-spinner color="primary"></ion-spinner> <ion-spinner color="primary"></ion-spinner>
<p class="core-loading-message" *ngIf="message" role="status">{{message}}</p> <p class="core-loading-message" *ngIf="message" role="status">{{message}}</p>
</div> </div>
<div #content class="core-loading-content" [id]="uniqueId" [attr.aria-busy]="hideUntil" [@coreShowHideAnimation]> <div #content class="core-loading-content" [id]="uniqueId" [attr.aria-busy]="!hideUntil" [@coreShowHideAnimation]
<ng-content *ngIf="hideUntil"> [class.opacity-hide]="!hideUntil">
<ng-content *ngIf="loaded">
</ng-content> </ng-content>
</div> </div>

View File

@ -43,6 +43,10 @@
flex-direction: column; flex-direction: column;
} }
.core-loading-content {
@include core-transition(opacity, 200ms);
}
.core-loading-message { .core-loading-message {
@include margin(10px, 0, 0, 0); @include margin(10px, 0, 0, 0);
} }

View File

@ -55,6 +55,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit {
uniqueId: string; uniqueId: string;
protected element: HTMLElement; // Current element. protected element: HTMLElement; // Current element.
loaded = false; // Only comes true once.
constructor(element: ElementRef) { constructor(element: ElementRef) {
this.element = element.nativeElement; this.element = element.nativeElement;
@ -83,6 +84,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit {
if (this.hideUntil) { if (this.hideUntil) {
this.element.classList.add('core-loading-loaded'); this.element.classList.add('core-loading-loaded');
} }
this.loaded = !!this.hideUntil;
this.content?.nativeElement.classList.toggle('core-loading-content', !!this.hideUntil); this.content?.nativeElement.classList.toggle('core-loading-content', !!this.hideUntil);
} }
@ -94,6 +96,10 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit {
*/ */
ngOnChanges(changes: { [name: string]: SimpleChange }): void { ngOnChanges(changes: { [name: string]: SimpleChange }): void {
if (changes.hideUntil) { if (changes.hideUntil) {
if (!this.loaded) {
this.loaded = !!this.hideUntil; // Only comes true once.
}
if (this.hideUntil) { if (this.hideUntil) {
setTimeout(() => { setTimeout(() => {
// Content is loaded so, center the spinner on the content itself. // Content is loaded so, center the spinner on the content itself.

View File

@ -34,7 +34,7 @@
<span class="sr-only">{{ 'core.login.sitebadgedescription' | translate:{ count: site.badge } }}</span> <span class="sr-only">{{ 'core.login.sitebadgedescription' | translate:{ count: site.badge } }}</span>
</ion-badge> </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" [@coreSlideInOut]="'fromRight'">
<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>
</ion-button> </ion-button>
</ion-item> </ion-item>

View File

@ -22,6 +22,7 @@ import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
import { CoreFilter } from '@features/filter/services/filter'; import { CoreFilter } from '@features/filter/services/filter';
import { CoreAnimations } from '@components/animations';
/** /**
* Page that displays a "splash screen" while the app is being initialized. * Page that displays a "splash screen" while the app is being initialized.
@ -29,6 +30,7 @@ import { CoreFilter } from '@features/filter/services/filter';
@Component({ @Component({
selector: 'page-core-login-sites', selector: 'page-core-login-sites',
templateUrl: 'sites.html', templateUrl: 'sites.html',
animations: [CoreAnimations.SLIDE_IN_OUT],
}) })
export class CoreLoginSitesPage implements OnInit { export class CoreLoginSitesPage implements OnInit {

View File

@ -63,8 +63,11 @@ export class CoreSettingsHelperProvider {
protected syncPromises: { [s: string]: Promise<void> } = {}; protected syncPromises: { [s: string]: Promise<void> } = {};
protected prefersDark?: MediaQueryList; protected prefersDark?: MediaQueryList;
protected colorSchemes: CoreColorScheme[] = []; protected colorSchemes: CoreColorScheme[] = [];
protected currentColorScheme = CoreColorScheme.LIGHT;
constructor() { constructor() {
this.prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
if (!CoreConstants.CONFIG.forceColorScheme) { if (!CoreConstants.CONFIG.forceColorScheme) {
// Update color scheme when a user enters or leaves a site, or when the site info is updated. // Update color scheme when a user enters or leaves a site, or when the site info is updated.
const applySiteScheme = (): void => { const applySiteScheme = (): void => {
@ -72,7 +75,6 @@ export class CoreSettingsHelperProvider {
// Dark mode is disabled, force light mode. // Dark mode is disabled, force light mode.
this.setColorScheme(CoreColorScheme.LIGHT); this.setColorScheme(CoreColorScheme.LIGHT);
} else { } else {
this.prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
// Reset color scheme settings. // Reset color scheme settings.
this.initColorScheme(); this.initColorScheme();
} }
@ -86,7 +88,12 @@ export class CoreSettingsHelperProvider {
// Reset color scheme settings. // Reset color scheme settings.
this.initColorScheme(); this.initColorScheme();
}); });
} else {
this.initColorScheme();
} }
// Listen for changes to the prefers-color-scheme media query.
this.prefersDark.addEventListener && this.prefersDark.addEventListener('change', this.toggleDarkModeListener.bind(this));
} }
/** /**
@ -432,17 +439,10 @@ export class CoreSettingsHelperProvider {
* @param colorScheme Name of the color scheme. * @param colorScheme Name of the color scheme.
*/ */
setColorScheme(colorScheme: CoreColorScheme): void { setColorScheme(colorScheme: CoreColorScheme): void {
this.currentColorScheme = colorScheme;
if (colorScheme == CoreColorScheme.SYSTEM && this.prefersDark) { if (colorScheme == CoreColorScheme.SYSTEM && this.prefersDark) {
// Listen for changes to the prefers-color-scheme media query.
this.prefersDark.addEventListener &&
this.prefersDark.addEventListener('change', this.toggleDarkModeListener);
this.toggleDarkMode(this.prefersDark.matches); this.toggleDarkMode(this.prefersDark.matches);
} else { } else {
// Stop listening to changes.
this.prefersDark?.removeEventListener &&
this.prefersDark?.removeEventListener('change', this.toggleDarkModeListener);
this.toggleDarkMode(colorScheme == CoreColorScheme.DARK); this.toggleDarkMode(colorScheme == CoreColorScheme.DARK);
} }
} }
@ -460,11 +460,9 @@ export class CoreSettingsHelperProvider {
/** /**
* Listener function to toggle dark mode. * Listener function to toggle dark mode.
*
* @param e Event object.
*/ */
protected toggleDarkModeListener = (e: MediaQueryListEvent): void => { protected toggleDarkModeListener(): void {
document.body.classList.toggle('dark', e.matches); this.setColorScheme(this.currentColorScheme);
}; };
/** /**

View File

@ -654,10 +654,17 @@ export class CoreAppProvider {
const useLightText = CoreColors.isWhiteContrastingBetter(color); const useLightText = CoreColors.isWhiteContrastingBetter(color);
const statusBar = StatusBar.instance; const statusBar = StatusBar.instance;
statusBar.backgroundColorByHexString(color);
useLightText ? statusBar.styleLightContent() : statusBar.styleDefault();
this.isIOS() && statusBar.overlaysWebView(false); this.isIOS() && statusBar.overlaysWebView(false);
// styleDefault will use white text on iOS when darkmode is on. Force the background to black.
if (this.isIOS() && !useLightText && window.matchMedia('(prefers-color-scheme: dark)').matches) {
statusBar.backgroundColorByHexString('#000000');
statusBar.styleLightContent();
} else {
statusBar.backgroundColorByHexString(color);
useLightText ? statusBar.styleLightContent() : statusBar.styleDefault();
}
} }
/** /**

View File

@ -180,6 +180,11 @@ core-format-text {
ion-icon { ion-icon {
flex: 1; flex: 1;
align-self: center; align-self: center;
/** Fix iOS icon size */
margin: 0 auto;
position: absolute;
left: 0;
right: 0;
} }
&:hover { &:hover {
@ -338,7 +343,6 @@ core-rich-text-editor .core-rte-editor {
select, select,
input:not([type=checkbox]):not([type=radio]):not([type=hidden]) { input:not([type=checkbox]):not([type=radio]):not([type=hidden]) {
height: 30px; height: 30px;
line-height: 30px;
display: inline-block; display: inline-block;
border: 1px solid var(--gray-dark); border: 1px solid var(--gray-dark);
background: var(--background-contrast); background: var(--background-contrast);