diff --git a/src/assets/fonts/moodle/moodle/swipe-navigation.svg b/src/assets/fonts/moodle/moodle/swipe-navigation.svg
new file mode 100644
index 000000000..e4c38b55b
--- /dev/null
+++ b/src/assets/fonts/moodle/moodle/swipe-navigation.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/user-tours/course-index.svg b/src/assets/img/user-tours/course-index.svg
deleted file mode 100644
index c3ecbf014..000000000
--- a/src/assets/img/user-tours/course-index.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/assets/img/user-tours/side-blocks.svg b/src/assets/img/user-tours/side-blocks.svg
deleted file mode 100644
index c2c8d4372..000000000
--- a/src/assets/img/user-tours/side-blocks.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/assets/img/user-tours/swipe-navigation.svg b/src/assets/img/user-tours/swipe-navigation.svg
deleted file mode 100644
index ac0c1732c..000000000
--- a/src/assets/img/user-tours/swipe-navigation.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/assets/img/user-tours/user-menu.svg b/src/assets/img/user-tours/user-menu.svg
deleted file mode 100644
index 33528d215..000000000
--- a/src/assets/img/user-tours/user-menu.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/core/components/swipe-navigation-tour/core-swipe-navigation-tour.html b/src/core/components/swipe-navigation-tour/core-swipe-navigation-tour.html
index e8b94a568..349247d15 100644
--- a/src/core/components/swipe-navigation-tour/core-swipe-navigation-tour.html
+++ b/src/core/components/swipe-navigation-tour/core-swipe-navigation-tour.html
@@ -1,5 +1,5 @@
-
+
{{ 'core.swipenavigationtourdescription' | translate }}
-
+
{{ 'core.endonesteptour' | translate }}
diff --git a/src/core/components/swipe-navigation-tour/swipe-navigation-tour.scss b/src/core/components/swipe-navigation-tour/swipe-navigation-tour.scss
index f71e25d12..66877c3df 100644
--- a/src/core/components/swipe-navigation-tour/swipe-navigation-tour.scss
+++ b/src/core/components/swipe-navigation-tour/swipe-navigation-tour.scss
@@ -1,12 +1,21 @@
:host {
max-width: 85vw;
- img {
- max-width: 300px;
+ ion-icon {
+ width: 100%;
+ height: 150px;
+ min-width: 300px;
}
p {
text-align: center;
}
+ ion-button {
+ --border-width: 2px;
+ --border-color: white;
+ --background: transparent;
+ --color: white;
+ }
+
}
diff --git a/src/core/features/block/components/side-blocks-tour/side-blocks-tour.html b/src/core/features/block/components/side-blocks-tour/side-blocks-tour.html
index 47596569a..00793c3ff 100644
--- a/src/core/features/block/components/side-blocks-tour/side-blocks-tour.html
+++ b/src/core/features/block/components/side-blocks-tour/side-blocks-tour.html
@@ -1,5 +1,4 @@
{{ 'core.block.tour_navigation_dashboard_title' | translate }}
-
{{ 'core.block.tour_navigation_dashboard_content' | translate }}
{{ 'core.endonesteptour' | translate }}
diff --git a/src/core/features/block/components/side-blocks-tour/side-blocks-tour.scss b/src/core/features/block/components/side-blocks-tour/side-blocks-tour.scss
index b94fabe85..e02e83905 100644
--- a/src/core/features/block/components/side-blocks-tour/side-blocks-tour.scss
+++ b/src/core/features/block/components/side-blocks-tour/side-blocks-tour.scss
@@ -4,7 +4,7 @@
margin-top: 0;
}
- p {
+ h2, p {
text-align: center;
}
diff --git a/src/core/features/course/components/course-index-tour/course-index-tour.html b/src/core/features/course/components/course-index-tour/course-index-tour.html
index c519d3152..dbf65101b 100644
--- a/src/core/features/course/components/course-index-tour/course-index-tour.html
+++ b/src/core/features/course/components/course-index-tour/course-index-tour.html
@@ -1,5 +1,4 @@
{{ 'core.course.tour_navigation_course_index_student_title' | translate }}
-
{{ 'core.course.tour_navigation_course_index_student_content' | translate }}
{{ 'core.endonesteptour' | translate }}
diff --git a/src/core/features/course/components/course-index-tour/course-index-tour.scss b/src/core/features/course/components/course-index-tour/course-index-tour.scss
index b94fabe85..e02e83905 100644
--- a/src/core/features/course/components/course-index-tour/course-index-tour.scss
+++ b/src/core/features/course/components/course-index-tour/course-index-tour.scss
@@ -4,7 +4,7 @@
margin-top: 0;
}
- p {
+ h2, p {
text-align: center;
}
diff --git a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts
index ebde9fb45..8c8efb728 100644
--- a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts
+++ b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts
@@ -14,8 +14,9 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { CoreSiteInfo } from '@classes/site';
-import { CoreUserTours, CoreUserToursStyle } from '@features/usertours/services/user-tours';
+import { CoreUserTours, CoreUserToursAlignment, CoreUserToursSide } from '@features/usertours/services/user-tours';
import { IonRouterOutlet } from '@ionic/angular';
+import { CoreScreen } from '@services/screen';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreMainMenuUserMenuTourComponent } from '../user-menu-tour/user-menu-tour';
@@ -77,7 +78,8 @@ export class CoreMainMenuUserButtonComponent implements OnInit {
id: 'user-menu',
component: CoreMainMenuUserMenuTourComponent,
focus: this.avatar.nativeElement,
- style: CoreUserToursStyle.Overlay,
+ alignment: CoreUserToursAlignment.Start,
+ side: CoreScreen.isMobile ? CoreUserToursSide.Start : CoreUserToursSide.End,
});
}
diff --git a/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.html b/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.html
index 697f56fcb..efef95b50 100644
--- a/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.html
+++ b/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.html
@@ -1,4 +1,3 @@
-
{{ 'core.mainmenu.usermenutourtitle' | translate }}
{{ 'core.mainmenu.usermenutourdescription' | translate }}
diff --git a/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.scss b/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.scss
index faecdd58f..e02e83905 100644
--- a/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.scss
+++ b/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.scss
@@ -1,26 +1,15 @@
:host {
- width: 100%;
- height: 100%;
- display: flex;
- max-width: 85vw;
- align-items: center;
- flex-direction: column;
- img {
- width: calc(100vw - var(--core-avatar-size) * 2 - 16px);
- margin-top: 12px;
+ h2 {
+ margin-top: 0;
}
- p {
+ h2, p {
text-align: center;
}
ion-button {
- width: 100%;
+ margin: 0;
}
}
-
-:host-context([dir=rtl]) img {
- transform: scaleX(-1);
-}
diff --git a/src/core/features/usertours/components/user-tour/user-tour.ts b/src/core/features/usertours/components/user-tour/user-tour.ts
index 5ac56633f..65f6e63dd 100644
--- a/src/core/features/usertours/components/user-tour/user-tour.ts
+++ b/src/core/features/usertours/components/user-tour/user-tour.ts
@@ -12,21 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { AfterViewInit, Component, ElementRef, HostBinding, Input, ViewChild } from '@angular/core';
+import { BackButtonEvent } from '@ionic/core';
+import { AfterViewInit, Component, ElementRef, EventEmitter, HostBinding, Input, Output, ViewChild } from '@angular/core';
import { CorePromisedValue } from '@classes/promised-value';
import { CoreUserToursFocusLayout } from '@features/usertours/classes/focus-layout';
import { CoreUserToursPopoverLayout } from '@features/usertours/classes/popover-layout';
-import {
- CoreUserTours,
- CoreUserToursAlignment,
- CoreUserToursSide,
- CoreUserToursStyle,
-} from '@features/usertours/services/user-tours';
+import { CoreUserTours, CoreUserToursAlignment, CoreUserToursSide } from '@features/usertours/services/user-tours';
import { CoreDomUtils } from '@services/utils/dom';
import { AngularFrameworkDelegate } from '@singletons';
import { CoreComponentsRegistry } from '@singletons/components-registry';
const ANIMATION_DURATION = 200;
+const USER_TOURS_BACK_BUTTON_PRIORITY = 100;
/**
* User Tour wrapper component.
@@ -45,9 +42,10 @@ export class CoreUserToursUserTourComponent implements AfterViewInit {
@Input() component!: unknown;
@Input() componentProps?: Record;
@Input() focus?: HTMLElement;
- @Input() style?: CoreUserToursStyle; // When this is undefined in a tour with a focused element, popover style will be used.
@Input() side?: CoreUserToursSide;
@Input() alignment?: CoreUserToursAlignment;
+ @Output() beforeDismiss = new EventEmitter();
+ @Output() afterDismiss = new EventEmitter();
@HostBinding('class.is-active') active = false;
@HostBinding('class.is-popover') popover = false;
@ViewChild('wrapper') wrapper?: ElementRef;
@@ -58,6 +56,7 @@ export class CoreUserToursUserTourComponent implements AfterViewInit {
private element: HTMLElement;
private wrapperTransform = '';
private wrapperElement = new CorePromisedValue();
+ private backButtonListener?: (event: BackButtonEvent) => void;
constructor({ nativeElement: element }: ElementRef) {
this.element = element;
@@ -86,11 +85,28 @@ export class CoreUserToursUserTourComponent implements AfterViewInit {
await CoreDomUtils.waitForImages(tour);
+ // Calculate focus styles or dismiss if the element is gone.
+ if (this.focus && !CoreDomUtils.isElementVisible(this.focus)) {
+ await this.dismiss(false);
+
+ return;
+ }
+
this.calculateStyles();
// Show tour.
this.active = true;
+ document.addEventListener(
+ 'ionBackButton',
+ this.backButtonListener = ({ detail }) => detail.register(
+ USER_TOURS_BACK_BUTTON_PRIORITY,
+ () => {
+ // Silence back button.
+ },
+ ),
+ );
+
await this.playEnterAnimation();
}
@@ -100,11 +116,16 @@ export class CoreUserToursUserTourComponent implements AfterViewInit {
* @param acknowledge Whether to confirm that the user has seen the User Tour.
*/
async dismiss(acknowledge: boolean = true): Promise {
+ this.beforeDismiss.emit();
+
await this.playLeaveAnimation();
+ await Promise.all([
+ AngularFrameworkDelegate.removeViewFromDom(this.container, this.element),
+ acknowledge && CoreUserTours.acknowledge(this.id),
+ ]);
- AngularFrameworkDelegate.removeViewFromDom(this.container, this.element);
-
- acknowledge && CoreUserTours.acknowledge(this.id);
+ this.backButtonListener && document.removeEventListener('ionBackButton', this.backButtonListener);
+ this.afterDismiss.emit();
}
/**
@@ -121,18 +142,16 @@ export class CoreUserToursUserTourComponent implements AfterViewInit {
this.focusStyles = focusLayout.inlineStyles;
// Calculate popup styles.
- if ((this.style ?? CoreUserToursStyle.Popover) === CoreUserToursStyle.Popover) {
- if (!this.side || !this.alignment) {
- throw new Error('Cannot create a popover user tour without side and alignment');
- }
-
- const popoverLayout = new CoreUserToursPopoverLayout(this.focus, this.side, this.alignment);
-
- this.popover = true;
- this.popoverWrapperStyles = popoverLayout.wrapperInlineStyles;
- this.popoverWrapperArrowStyles = popoverLayout.wrapperArrowInlineStyles;
- this.wrapperTransform = `${popoverLayout.wrapperStyles.transform ?? ''}`;
+ if (!this.side || !this.alignment) {
+ throw new Error('Cannot create a focused user tour without side and alignment');
}
+
+ const popoverLayout = new CoreUserToursPopoverLayout(this.focus, this.side, this.alignment);
+
+ this.popover = true;
+ this.popoverWrapperStyles = popoverLayout.wrapperInlineStyles;
+ this.popoverWrapperArrowStyles = popoverLayout.wrapperArrowInlineStyles;
+ this.wrapperTransform = `${popoverLayout.wrapperStyles.transform ?? ''}`;
}
/**
diff --git a/src/core/features/usertours/services/user-tours.ts b/src/core/features/usertours/services/user-tours.ts
index ff42e12f1..749287f1b 100644
--- a/src/core/features/usertours/services/user-tours.ts
+++ b/src/core/features/usertours/services/user-tours.ts
@@ -21,6 +21,7 @@ import { CoreApp } from '@services/app';
import { CoreUtils } from '@services/utils/utils';
import { AngularFrameworkDelegate, makeSingleton } from '@singletons';
import { CoreComponentsRegistry } from '@singletons/components-registry';
+import { CoreSubscriptions } from '@singletons/subscriptions';
import { CoreUserToursUserTourComponent } from '../components/user-tour/user-tour';
import { APP_SCHEMA, CoreUserToursDBEntry, USER_TOURS_TABLE_NAME } from './database/user-tours';
@@ -85,9 +86,8 @@ export class CoreUserToursService {
* @param options User Tour options.
*/
async showIfPending(options: CoreUserToursBasicOptions): Promise;
- async showIfPending(options: CoreUserToursPopoverFocusedOptions): Promise;
- async showIfPending(options: CoreUserToursOverlayFocusedOptions): Promise;
- async showIfPending(options: CoreUserToursOptions): Promise {
+ async showIfPending(options: CoreUserToursFocusedOptions): Promise;
+ async showIfPending(options: CoreUserToursBasicOptions | CoreUserToursFocusedOptions): Promise {
const isPending = await CoreUserTours.isPending(options.id);
if (!isPending) {
@@ -103,13 +103,14 @@ export class CoreUserToursService {
* @param options User Tour options.
*/
protected async show(options: CoreUserToursBasicOptions): Promise;
- protected async show(options: CoreUserToursPopoverFocusedOptions): Promise;
- protected async show(options: CoreUserToursOverlayFocusedOptions): Promise;
- protected async show(options: CoreUserToursOptions): Promise {
+ protected async show(options: CoreUserToursFocusedOptions): Promise;
+ protected async show(options: CoreUserToursBasicOptions | CoreUserToursFocusedOptions): Promise {
const { delay, ...componentOptions } = options;
+ // Delay start.
await CoreUtils.wait(delay ?? 200);
+ // Create tour.
const container = document.querySelector('ion-app') ?? document.body;
const element = await AngularFrameworkDelegate.attachViewToDom(
container,
@@ -119,6 +120,22 @@ export class CoreUserToursService {
const tour = CoreComponentsRegistry.require(element, CoreUserToursUserTourComponent);
this.tours.push(tour);
+
+ // Handle present/dismiss lifecycle.
+ CoreSubscriptions.once(tour.beforeDismiss, () => {
+ const index = this.tours.indexOf(tour);
+
+ if (index === -1) {
+ return;
+ }
+
+ this.tours.splice(index, 1);
+
+ const nextTour = this.tours[0] as CoreUserToursUserTourComponent | undefined;
+
+ nextTour?.present().then(() => this.tourReadyCallbacks.get(nextTour)?.());
+ });
+
this.tours.length > 1
? await new Promise(resolve => this.tourReadyCallbacks.set(tour, resolve))
: await tour.present();
@@ -130,33 +147,13 @@ export class CoreUserToursService {
* @param acknowledge Whether to acknowledge that the user has seen this User Tour or not.
*/
async dismiss(acknowledge: boolean = true): Promise {
- if (this.tours.length === 0) {
- return;
- }
-
- const activeTour = this.tours.shift() as CoreUserToursUserTourComponent;
- const nextTour = this.tours[0] as CoreUserToursUserTourComponent | undefined;
-
- await Promise.all([
- activeTour.dismiss(acknowledge),
- nextTour?.present(),
- ]);
-
- nextTour && this.tourReadyCallbacks.get(nextTour)?.();
+ await this.tours[0]?.dismiss(acknowledge);
}
}
export const CoreUserTours = makeSingleton(CoreUserToursService);
-/**
- * User Tour style.
- */
-export const enum CoreUserToursStyle {
- Overlay = 'overlay',
- Popover = 'popover',
-}
-
/**
* User Tour side.
*/
@@ -217,18 +214,6 @@ export interface CoreUserToursFocusedOptions extends CoreUserToursBasicOptions {
*/
focus: HTMLElement;
-}
-
-/**
- * Options to create a focused User Tour using the Popover style.
- */
-export interface CoreUserToursPopoverFocusedOptions extends CoreUserToursFocusedOptions {
-
- /**
- * User Tour style.
- */
- style?: CoreUserToursStyle.Popover;
-
/**
* Position relative to the focused element.
*/
@@ -240,23 +225,3 @@ export interface CoreUserToursPopoverFocusedOptions extends CoreUserToursFocused
alignment: CoreUserToursAlignment;
}
-
-/**
- * Options to create a focused User Tour using the Overlay style.
- */
-export interface CoreUserToursOverlayFocusedOptions extends CoreUserToursFocusedOptions {
-
- /**
- * User Tour style.
- */
- style: CoreUserToursStyle.Overlay;
-
-}
-
-/**
- * Options to create a User Tour.
- */
-export type CoreUserToursOptions =
- CoreUserToursBasicOptions |
- CoreUserToursPopoverFocusedOptions |
- CoreUserToursOverlayFocusedOptions;