diff --git a/src/core/features/h5p/components/h5p-iframe/core-h5p-iframe.html b/src/core/features/h5p/components/h5p-iframe/core-h5p-iframe.html
index acb3d16ff..52020bec3 100644
--- a/src/core/features/h5p/components/h5p-iframe/core-h5p-iframe.html
+++ b/src/core/features/h5p/components/h5p-iframe/core-h5p-iframe.html
@@ -1,4 +1,4 @@
-
+
diff --git a/src/core/features/mainmenu/pages/menu/menu.scss b/src/core/features/mainmenu/pages/menu/menu.scss
index 3bb190a5e..53f005f0d 100644
--- a/src/core/features/mainmenu/pages/menu/menu.scss
+++ b/src/core/features/mainmenu/pages/menu/menu.scss
@@ -51,12 +51,14 @@
ion-tab-bar {
order: -1;
width: var(--menutabbar-size);
- height: 100%;
+ height: calc(100% - var(--ion-safe-area-top) - var(--ion-safe-area-bottom));
flex-direction: column;
@include border-end(var(--border));
box-shadow: 3px 0 3px rgba(var(--drop-shadow));
border-top: 0;
+ @include padding(var(--ion-safe-area-top), 0px, var(--ion-safe-area-bottom), var(--ion-safe-area-left));
+
ion-tab-button {
width: 100%;
height: auto;
@@ -68,6 +70,7 @@
}
.core-network-message {
+ --network-message-height: 16px;
position: absolute;
bottom: 0;
left: 0;
@@ -80,6 +83,7 @@
height: 0;
transition: all 500ms ease-in-out;
opacity: .8;
+ z-index: 12;
}
.core-online-message,
@@ -100,7 +104,8 @@
.core-network-message {
visibility: visible;
- height: 16px;
+ height: var(--network-message-height);
+ padding-bottom: calc(var(--ion-safe-area-bottom, 0px) + var(--network-message-height));
pointer-events: none;
}
}
diff --git a/src/core/features/search/components/search-box/search-box.scss b/src/core/features/search/components/search-box/search-box.scss
index 2501ff47a..e3d84473a 100644
--- a/src/core/features/search/components/search-box/search-box.scss
+++ b/src/core/features/search/components/search-box/search-box.scss
@@ -32,8 +32,8 @@
}
ion-input {
- --padding-start: 0;
- --padding-end: 0;
+ --padding-start: 0px;
+ --padding-end: 0px;
padding-left: 0;
padding-right: 0;
}
diff --git a/src/core/features/settings/services/settings-helper.ts b/src/core/features/settings/services/settings-helper.ts
index ee07046d5..51e7a9c1f 100644
--- a/src/core/features/settings/services/settings-helper.ts
+++ b/src/core/features/settings/services/settings-helper.ts
@@ -388,8 +388,8 @@ export class CoreSettingsHelperProvider {
*/
applyZoomLevel(zoomLevel: CoreZoomLevel): void {
const zoom = CoreConstants.CONFIG.zoomlevels[zoomLevel];
- // @todo MOBILE-3790 non-standard property, doesn't work everywhere.
- document.documentElement.style.zoom = zoom + '%';
+
+ document.documentElement.style.setProperty('--zoom-level', zoom + '%');
}
/**
diff --git a/src/core/features/siteplugins/components/plugin-content/core-siteplugins-plugin-content.html b/src/core/features/siteplugins/components/plugin-content/core-siteplugins-plugin-content.html
index 95f612eff..2126b11fb 100644
--- a/src/core/features/siteplugins/components/plugin-content/core-siteplugins-plugin-content.html
+++ b/src/core/features/siteplugins/components/plugin-content/core-siteplugins-plugin-content.html
@@ -1,4 +1,4 @@
-
+
diff --git a/src/core/features/tag/pages/search/search.html b/src/core/features/tag/pages/search/search.html
index e4b26e7bd..5afa3e371 100644
--- a/src/core/features/tag/pages/search/search.html
+++ b/src/core/features/tag/pages/search/search.html
@@ -10,7 +10,7 @@
-
+
1 ? '' : null">
-
+
diff --git a/src/core/features/viewer/components/text/text.html b/src/core/features/viewer/components/text/text.html
index cda8ec03e..10248ad3f 100644
--- a/src/core/features/viewer/components/text/text.html
+++ b/src/core/features/viewer/components/text/text.html
@@ -18,7 +18,7 @@
-
+
{{ 'core.copytoclipboard' | translate }}
diff --git a/src/core/initializers/watch-screen-viewport.ts b/src/core/initializers/watch-screen-status.ts
similarity index 95%
rename from src/core/initializers/watch-screen-viewport.ts
rename to src/core/initializers/watch-screen-status.ts
index 23e1210b0..ee4d5fcf1 100644
--- a/src/core/initializers/watch-screen-viewport.ts
+++ b/src/core/initializers/watch-screen-status.ts
@@ -16,4 +16,6 @@ import { CoreScreen } from '@services/screen';
export default function(): void {
CoreScreen.watchViewport();
+
+ CoreScreen.watchOrientation();
}
diff --git a/src/core/lang.json b/src/core/lang.json
index dff3a1052..02beb3878 100644
--- a/src/core/lang.json
+++ b/src/core/lang.json
@@ -82,6 +82,7 @@
"dftimedate": "h[:]mm A",
"digitalminor": "Digital minor",
"digitalminor_desc": "Please ask your parent/guardian to contact:",
+ "disablefullscreen": "Disable fullscreen",
"discard": "Discard",
"dismiss": "Dismiss",
"displayoptions": "Display options",
@@ -123,6 +124,7 @@
"forcepasswordchangenotice": "You must change your password to proceed.",
"fulllistofcourses": "All courses",
"fullnameandsitename": "{{fullname}} ({{sitename}})",
+ "fullscreen": "Fullscreen",
"group": "Group",
"groupsseparate": "Separate groups",
"groupsvisible": "Visible groups",
diff --git a/src/core/services/screen.ts b/src/core/services/screen.ts
index b53dcbcb7..6228f7700 100644
--- a/src/core/services/screen.ts
+++ b/src/core/services/screen.ts
@@ -17,6 +17,7 @@ import { BehaviorSubject, Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { makeSingleton } from '@singletons';
+import { CoreEvents } from '@singletons/events';
/**
* Screen breakpoints.
@@ -48,6 +49,14 @@ export enum CoreScreenLayout {
TABLET = 'tablet',
}
+/**
+ * Screen orientation.
+ */
+export enum CoreScreenOrientation {
+ LANDSCAPE = 'landscape',
+ PORTRAIT = 'portrait',
+}
+
/**
* Manage application screen.
*/
@@ -93,6 +102,32 @@ export class CoreScreenService {
return this.layout === CoreScreenLayout.TABLET;
}
+ get orientation(): CoreScreenOrientation {
+ const mql = window.matchMedia('(orientation: portrait)');
+
+ return mql.matches ? CoreScreenOrientation.PORTRAIT : CoreScreenOrientation.LANDSCAPE;
+ }
+
+ get isPortrait(): boolean {
+ return this.orientation === CoreScreenOrientation.PORTRAIT;
+ }
+
+ get isLandscape(): boolean {
+ return this.orientation === CoreScreenOrientation.LANDSCAPE;
+ }
+
+ /**
+ * Watch orientation changes.
+ */
+ watchOrientation(): void {
+ // Listen media orientation CSS queries.
+ window.matchMedia('(orientation: portrait)').addEventListener('change', (m) => {
+ const orientation = m.matches ? CoreScreenOrientation.PORTRAIT : CoreScreenOrientation.LANDSCAPE;
+
+ CoreEvents.trigger(CoreEvents.ORIENTATION_CHANGE, { orientation });
+ });
+ }
+
/**
* Watch viewport changes.
*/
diff --git a/src/core/singletons/events.ts b/src/core/singletons/events.ts
index 0c622f77b..49aa650a5 100644
--- a/src/core/singletons/events.ts
+++ b/src/core/singletons/events.ts
@@ -19,6 +19,7 @@ import { CoreSite, CoreSiteInfoResponse, CoreSitePublicConfigResponse } from '@c
import { CoreFilepoolComponentFileEventData } from '@services/filepool';
import { CoreNavigationOptions } from '@services/navigator';
import { CoreCourseModuleCompletionData } from '@features/course/services/course-helper';
+import { CoreScreenOrientation } from '@services/screen';
/**
* Observer instance to stop listening to an event.
@@ -55,6 +56,7 @@ export interface CoreEventsData {
[CoreEvents.COMPONENT_FILE_ACTION]: CoreFilepoolComponentFileEventData;
[CoreEvents.FILE_SHARED]: CoreEventFileSharedData;
[CoreEvents.APP_LAUNCHED_URL]: CoreEventAppLaunchedData;
+ [CoreEvents.ORIENTATION_CHANGE]: CoreEventOrientationData;
}
/*
@@ -387,3 +389,10 @@ export type CoreEventFileSharedData = {
export type CoreEventAppLaunchedData = {
url: string;
};
+
+/**
+ * Data passed to ORIENTATION_CHANGE event.
+ */
+export type CoreEventOrientationData = {
+ orientation: CoreScreenOrientation;
+};
diff --git a/src/theme/components/discussion.scss b/src/theme/components/discussion.scss
index 66f450ba9..bf359a84b 100644
--- a/src/theme/components/discussion.scss
+++ b/src/theme/components/discussion.scss
@@ -39,8 +39,8 @@
overflow: visible;
&::part(native) {
- --inner-border-width: 0;
- --inner-padding-end: 0;
+ --inner-border-width: 0px;
+ --inner-padding-end: 0px;
padding: 0;
margin: 0;
}
diff --git a/src/theme/globals.mixins.scss b/src/theme/globals.mixins.scss
index 52031a533..c871f18b3 100644
--- a/src/theme/globals.mixins.scss
+++ b/src/theme/globals.mixins.scss
@@ -50,100 +50,93 @@
}
@mixin border-start($px, $type: null, $color: null) {
- @include ltr() {
- border-left: $px $type $color;
- }
+ border-left: $px $type $color;
@include rtl() {
+ border-left: unset;
border-right: $px $type $color;
}
}
@mixin border-end($px, $type: null, $color: null) {
- @include ltr() {
- border-right: $px $type $color;
- }
+ border-right: $px $type $color;
@include rtl() {
+ border-right: unset;
border-left: $px $type $color;
}
}
@mixin safe-area-border-start($px, $type, $color) {
- $safe-area-position: calc(constant(safe-area-inset-left) + #{$px});
- $safe-area-position-env: calc(env(safe-area-inset-left) + #{$px});
+ $safe-area-position: calc(var(--ion-safe-area-left) + #{$px});
- @include border-start($px, $type, $color);
- @media screen and (orientation: landscape) {
- @include border-start($safe-area-position, $type, $color);
- @include border-start($safe-area-position-env, $type, $color);
- }
+ @include border-start($safe-area-position, $type, $color);
}
@mixin safe-area-border-end($px, $type, $color) {
- $safe-area-position: calc(constant(safe-area-inset-right) + #{$px});
- $safe-area-position-env: calc(env(safe-area-inset-right) + #{$px});
+ $safe-area-position: calc(var(--ion-safe-area-right) + #{$px});
- @include border-end($px, $type, $color);
- @media screen and (orientation: landscape) {
- @include border-end($safe-area-position, $type, $color);
- @include border-end($safe-area-position-env, $type, $color);
- }
+ @include border-end($safe-area-position, $type, $color);
}
@mixin safe-area-margin-horizontal($start, $end: $start) {
$safe-area-end: null;
$safe-area-start: null;
- $safe-area-start-env: null;
- $safe-area-end-env: null;
@if ($end) {
- $safe-area-end: calc(constant(safe-area-inset-right) + #{$end});
- $safe-area-end-env: calc(env(safe-area-inset-right) + #{$end});
+ $safe-area-end: calc(var(--ion-safe-area-right) + #{$end});
}
@if ($start) {
- $safe-area-start: calc(constant(safe-area-inset-left) + #{$start});
- $safe-area-start-env: calc(env(safe-area-inset-left) + #{$start});
+ $safe-area-start: calc(var(--ion-safe-area-left) + #{$start});
}
- @include margin-horizontal($start, $end);
+ @include margin-horizontal($safe-area-start, $safe-area-end);
+}
- @media screen and (orientation: landscape) {
- @include margin-horizontal($safe-area-start, $safe-area-end);
- @include margin-horizontal($safe-area-start-env, $safe-area-end-env);
+@mixin safe-area-margin-start($start, $end) {
+ $safe-area-start: calc(var(--ion-safe-area-left) + #{$start});
+
+ @include margin-horizontal($safe-area-start, $end);
+}
+
+@mixin safe-area-margin-end($start, $end) {
+ $safe-area-end: calc(var(--ion-safe-area-right) + #{$end});
+
+ @include margin-horizontal($start, $safe-area-end);
+}
+
+@mixin safe-area-padding-horizontal($start, $end: $start) {
+ $safe-area-end: null;
+ $safe-area-start: null;
+
+ @if ($end) {
+ $safe-area-end: calc(var(--ion-safe-area-right) + #{$end});
}
+ @if ($start) {
+ $safe-area-start: calc(var(--ion-safe-area-left) + #{$start});
+ }
+
+ @include padding-horizontal($safe-area-start, $safe-area-end);
}
@mixin safe-area-padding-start($start, $end) {
- $safe-area-start: calc(constant(safe-area-inset-left) + #{$start});
- $safe-area-start-env: calc(env(safe-area-inset-left) + #{$start});
+ $safe-area-start: calc(var(--ion-safe-area-left) + #{$start});
- @include padding-horizontal($start, $end);
-
- @media screen and (orientation: landscape) {
- @include padding-horizontal($safe-area-start, $end);
- @include padding-horizontal($safe-area-start-env, $end);
- }
+ @include padding-horizontal($safe-area-start, $end);
}
@mixin safe-area-padding-end($start, $end) {
- $safe-area-end: calc(constant(safe-area-inset-right) + #{$end});
- $safe-area-end-env: calc(env(safe-area-inset-right) + #{$end});
+ $safe-area-end: calc(var(--ion-safe-area-right) + #{$end});
- @include padding-horizontal($start, $end);
-
- @media screen and (orientation: landscape) {
- @include padding-horizontal($start, $safe-area-end);
- @include padding-horizontal($start, $safe-area-end-env);
- }
+ @include padding-horizontal($start, $safe-area-end);
}
@mixin safe-area-position($top: null, $end: null, $bottom: null, $start: null) {
- @include position-horizontal($start, $end);
- @include safe-position-horizontal($start, $end);
- top: $top;
- bottom: $bottom;
+ $safe-area-start: calc(var(--ion-safe-area-left) + #{$start});
+ $safe-area-end: calc(var(--ion-safe-area-right) + #{$end});
+
+ @include position($top, $safe-area-end, $bottom, $safe-area-start);
}
@mixin core-headings() {
diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss
index 0253a989c..c040c18d3 100644
--- a/src/theme/theme.base.scss
+++ b/src/theme/theme.base.scss
@@ -1,7 +1,19 @@
@import "./globals.scss";
+html.force-safe-area-margins {
+ --ion-safe-area-left: 40px;
+ --ion-safe-area-right: 40px;
+ --ion-safe-area-top: 40px;
+ --ion-safe-area-bottom: 40px;
+}
+
+// @todo MOBILE-3790 non-standard property, doesn't work everywhere.
+html {
+ zoom: var(--zoom-level);
+}
+
body {
- -webkit-text-size-adjust: auto;
+ -webkit-text-size-adjust: var(--zoom-level);
}
// Common styles.
@@ -234,6 +246,10 @@ ion-header ion-toolbar {
}
}
+ion-footer ion-toolbar.ion-color-contrast {
+ background-color: var(--contrast-background);
+}
+
// Ionic icon.
ion-icon {
position: relative;
@@ -374,7 +390,48 @@ ion-list.list-md {
padding: 0;
}
+// Safe areas
+.safe-area-padding,
+.safe-area-padding-horizontal {
+ @include padding-horizontal(var(--ion-safe-area-left), var(--ion-safe-area-right));
+}
+
+.safe-area-margin,
+.safe-margin-horizontal {
+ @include margin-horizontal(var(--ion-safe-area-left), var(--ion-safe-area-right));
+}
+
+.ion-padding.safe-area-padding-horizontal {
+ @include safe-area-padding-horizontal(16px, 16px);
+}
+
+.ion-margin.safe-margin-horizontal {
+ @include safe-area-margin-horizontal(16px, 16px);
+}
+
+ion-tabs.placement-side .tabs-inner {
+ --ion-safe-area-left: 0px;
+}
+
+ion-tabs.placement-bottom .tabs-inner {
+ --ion-safe-area-bottom: 0px;
+}
+
+
+core-split-view.menu-and-content {
+ .menu {
+ --ion-safe-area-right: 0px;
+ }
+
+ .content-outlet {
+ --ion-safe-area-left: 0px;
+ }
+}
+
// Header.
+ion-header {
+ z-index: 12; // To hide ion-slides on scroll.
+}
ion-tabs.hide-header ion-header {
display: none;
}
@@ -384,6 +441,60 @@ ion-toolbar {
}
}
+// Iframe fullscreen manage.
+// Using router outlet to avoid changing styles on modals.
+body.core-iframe-fullscreen ion-router-outlet {
+
+ ion-tab-bar.mainmenu-tabs {
+ display: none;
+
+ // Restore original safe area.
+ .tabs-inner {
+ @supports (padding-left: constant(safe-area-inset-left)) {
+ --ion-safe-area-left: constant(safe-area-inset-left);
+ }
+
+ @supports (padding-left: env(safe-area-inset-left)) {
+ --ion-safe-area-left: env(safe-area-inset-left);
+ }
+ }
+ }
+
+ --core-header-toolbar-height: 48px;
+ --core-header-toolbar-color: white;
+ --core-header-toolbar-background: black;
+ --core-header-toolbar-border-width: 0px;
+
+ ion-header ion-toolbar {
+ h1, ion-back-button {
+ display: none;
+ }
+ }
+
+ @media screen and (orientation: landscape) {
+ // Place ion-header on the side and hide text
+ .ion-page {
+ flex-direction: row-reverse;
+ ion-header {
+ width: calc(var(--core-header-toolbar-height), var(--ion-safe-area-right));
+ @include safe-area-padding-horizontal(null, 0px);
+ background: var(--core-header-toolbar-background);
+
+ ion-toolbar {
+ padding: 0;
+ height: 100%;
+ --padding-start: 0px;
+ --padding-end: 0px;
+ }
+
+ ion-buttons {
+ flex-direction: column-reverse;
+ }
+ }
+ }
+ }
+}
+
// Modals.
.core-modal-fullscreen .modal-wrapper {
position: absolute;
@@ -399,6 +510,8 @@ ion-toolbar {
@media only screen and (min-height: 400px) and (min-width: 300px) {
.core-modal-lateral {
+ --ion-safe-area-left: 0px;
+ --ion-safe-area-right: 0px;
.modal-wrapper {
position: absolute;
@@ -434,9 +547,14 @@ ion-toolbar {
// Item styles
[aria-current="page"],
.item.item-current {
- // TODO: Add safe area to border and RTL
- --ion-safe-area-left: calc(-1 * var(--selected-item-border-width));
- border-inline-start: var(--selected-item-border-width) solid var(--selected-item-color);
+ @include safe-area-border-start(var(--selected-item-border-width), solid, var(--selected-item-color));
+ > * {
+ --ion-safe-area-left: 0px;
+ }
+
+ &::part(native) {
+ --ion-safe-area-left: 0px;
+ }
}
.item.item-file {
@@ -467,7 +585,7 @@ ion-toolbar {
border-bottom: 3px solid var(--color-base);
ion-item {
- --inner-border-width: 0;
+ --inner-border-width: 0px;
}
ion-label {
white-space: normal !important;
@@ -482,7 +600,7 @@ ion-toolbar {
--border-width: 0 0 3px 0;
--border-color: var(--color-base);
- --inner-border-width: 0;
+ --inner-border-width: 0px;
ion-icon {
color: var(--color-base);
}
@@ -528,7 +646,7 @@ img[core-external-content]:not([src]) {
}
ion-card ion-item:only-child {
- --inner-border-width: 0;
+ --inner-border-width: 0px;
}
.core-course-module-handler:not(.addon-mod-label-handler) .item-heading .filter_mathjaxloader_equation div {
@@ -686,10 +804,14 @@ core-block ion-item-divider .core-button-spinner {
// Horizontal scrolling elements
.core-horizontal-scroll {
- display: flex;
- flex-flow: nowrap;
+ display: block;
overflow-x: scroll;
- flex-direction: row;
+ .safe-area-pseudo-padding-start {
+ @include padding-horizontal(var(--ion-safe-area-left), 0px);
+ }
+ .safe-area-pseudo-padding-end {
+ @include padding-horizontal(0px, var(--ion-safe-area-right));
+ }
}
// Text formats.
@@ -952,6 +1074,11 @@ ion-item.item-input ion-input.has-focus {
}
}
+// Ionic set this value to 0 without px that provoked miscalculations.
+ion-item-divider {
+ --inner-padding-end: 0px;
+}
+
// Change default outline.
:focus-visible {
@include core-focus-style();
diff --git a/src/theme/theme.light.scss b/src/theme/theme.light.scss
index 5c4114512..fb0ad53ec 100644
--- a/src/theme/theme.light.scss
+++ b/src/theme/theme.light.scss
@@ -61,6 +61,7 @@
--a11y-min-target-size: 44px;
--a11y-focus-color: var(--primary);
--a11y-focus-width: 2px;
+ --zoom-level: 100%;
--module-icon-size: 24px;
@@ -236,12 +237,17 @@
--color: var(--subdued-text-color);
}
+ ion-back-button {
+ --min-height: var(--a11y-min-target-size);
+ --min-width: var(--a11y-min-target-size);
+ }
+
--core-combobox-background: var(--ion-item-background);
--core-combobox-color: var(--black);
--core-combobox-border-color: var(--primary);
--core-combobox-border-width: 3px;
--core-combobox-border-all-width: 0 0 var(--core-combobox-border-width) 0;
- --core-combobox-radius: 0;
+ --core-combobox-radius: 0px;
--core-combobox-box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .2), 0 2px 2px 0 rgba(0, 0, 0, .14), 0 1px 5px 0 rgba(0, 0, 0, .12);
--selected-item-color: var(--primary);