From 3249647f2a1bfd35b5fd98eb26fcc93b939fcbfe Mon Sep 17 00:00:00 2001
From: Dani Palou <dani@moodle.com>
Date: Fri, 5 Mar 2021 14:48:00 +0100
Subject: [PATCH] MOBILE-3664 siteplugins: Support home delegate for plugins

---
 src/core/classes/tabs.ts                      |  4 +-
 .../components/tabs-outlet/tabs-outlet.ts     | 26 ++++-----
 .../handlers/main-menu-home-handler.ts        | 58 +++++++++++++++++++
 .../services/siteplugins-helper.ts            | 42 ++++++++++++++
 .../siteplugins/services/siteplugins.ts       | 14 ++++-
 .../siteplugins/siteplugins.module.ts         |  2 +
 6 files changed, 128 insertions(+), 18 deletions(-)
 create mode 100644 src/core/features/siteplugins/classes/handlers/main-menu-home-handler.ts

diff --git a/src/core/classes/tabs.ts b/src/core/classes/tabs.ts
index 8cc91af13..c04bea315 100644
--- a/src/core/classes/tabs.ts
+++ b/src/core/classes/tabs.ts
@@ -23,6 +23,7 @@ import {
     AfterViewInit,
     ViewChild,
     ElementRef,
+    SimpleChange,
 } from '@angular/core';
 import { IonSlides } from '@ionic/angular';
 import { BackButtonEvent } from '@ionic/core';
@@ -153,7 +154,8 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
     /**
      * Detect changes on input properties.
      */
-    ngOnChanges(): void {
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    ngOnChanges(changes: Record<string, SimpleChange>): void {
         // Wait for ngAfterViewInit so it works in the case that each tab has its own component.
         if (!this.initialized && this.hideUntil && this.afterViewInitTriggered) {
             // Tabs should be shown, initialize them.
diff --git a/src/core/components/tabs-outlet/tabs-outlet.ts b/src/core/components/tabs-outlet/tabs-outlet.ts
index 0fd52aaa9..963cb3ced 100644
--- a/src/core/components/tabs-outlet/tabs-outlet.ts
+++ b/src/core/components/tabs-outlet/tabs-outlet.ts
@@ -21,6 +21,7 @@ import {
     AfterViewInit,
     ViewChild,
     ElementRef,
+    SimpleChange,
 } from '@angular/core';
 import { IonTabs } from '@ionic/angular';
 import { Subscription } from 'rxjs';
@@ -70,17 +71,6 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent<CoreTabsOutle
         super(element);
     }
 
-    /**
-     * Component being initialized.
-     */
-    async ngOnInit(): Promise<void> {
-        super.ngOnInit();
-
-        this.tabs.forEach((tab) => {
-            this.initTab(tab);
-        });
-    }
-
     /**
      * Init tab info.
      *
@@ -117,12 +107,16 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent<CoreTabsOutle
     /**
      * Detect changes on input properties.
      */
-    ngOnChanges(): void {
-        this.tabs.forEach((tab) => {
-            this.initTab(tab);
-        });
+    ngOnChanges(changes: Record<string, SimpleChange>): void {
+        if (changes.tabs) {
+            this.tabs.forEach((tab) => {
+                this.initTab(tab);
+            });
 
-        super.ngOnChanges();
+            this.calculateSlides();
+        }
+
+        super.ngOnChanges(changes);
     }
 
     /**
diff --git a/src/core/features/siteplugins/classes/handlers/main-menu-home-handler.ts b/src/core/features/siteplugins/classes/handlers/main-menu-home-handler.ts
new file mode 100644
index 000000000..1e2904b9d
--- /dev/null
+++ b/src/core/features/siteplugins/classes/handlers/main-menu-home-handler.ts
@@ -0,0 +1,58 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { CoreMainMenuHomeHandler, CoreMainMenuHomeHandlerData } from '@features/mainmenu/services/home-delegate';
+import {
+    CoreSitePluginsContent,
+    CoreSitePluginsMainMenuHomeHandlerData,
+    CoreSitePluginsPlugin,
+} from '@features/siteplugins/services/siteplugins';
+import { CoreSitePluginsBaseHandler } from './base-handler';
+
+/**
+ * Handler to display a site plugin in the main menu.
+ */
+export class CoreSitePluginsMainMenuHomeHandler extends CoreSitePluginsBaseHandler implements CoreMainMenuHomeHandler {
+
+    priority: number;
+
+    constructor(
+        name: string,
+        protected title: string,
+        protected plugin: CoreSitePluginsPlugin,
+        protected handlerSchema: CoreSitePluginsMainMenuHomeHandlerData,
+        protected initResult: CoreSitePluginsContent | null,
+    ) {
+        super(name);
+
+        this.priority = handlerSchema.priority || 0;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    getDisplayData(): CoreMainMenuHomeHandlerData {
+        return {
+            title: this.title,
+            class: this.handlerSchema.displaydata?.class,
+            page: `siteplugins/${this.plugin.component}/${this.handlerSchema.method}/0`,
+            pageParams: {
+                title: this.title,
+                initResult: this.initResult,
+                ptrEnabled: this.handlerSchema.ptrenabled,
+            },
+        };
+    }
+
+}
diff --git a/src/core/features/siteplugins/services/siteplugins-helper.ts b/src/core/features/siteplugins/services/siteplugins-helper.ts
index 3c4c06eeb..439340e14 100644
--- a/src/core/features/siteplugins/services/siteplugins-helper.ts
+++ b/src/core/features/siteplugins/services/siteplugins-helper.ts
@@ -74,8 +74,11 @@ import {
     CoreSitePluginsBlockHandlerData,
     CoreSitePluginsHandlerCommonData,
     CoreSitePluginsInitHandlerData,
+    CoreSitePluginsMainMenuHomeHandlerData,
 } from './siteplugins';
 import { makeSingleton } from '@singletons';
+import { CoreMainMenuHomeDelegate } from '@features/mainmenu/services/home-delegate';
+import { CoreSitePluginsMainMenuHomeHandler } from '../classes/handlers/main-menu-home-handler';
 
 const HANDLER_DISABLED = 'core_site_plugins_helper_handler_disabled';
 
@@ -535,6 +538,10 @@ export class CoreSitePluginsHelperProvider {
                     uniqueName = await this.registerWorkshopAssessmentStrategyHandler(plugin, handlerName, handlerSchema);
                     break;
 
+                case 'CoreMainMenuHomeDelegate':
+                    uniqueName = await this.registerMainMenuHomeHandler(plugin, handlerName, handlerSchema, initResult);
+                    break;
+
                 default:
                     // Nothing to do.
             }
@@ -1130,6 +1137,41 @@ export class CoreSitePluginsHelperProvider {
         CoreEvents.trigger(CoreEvents.SITE_PLUGINS_COURSE_RESTRICT_UPDATED, {});
     }
 
+    /**
+     * Given a handler in a plugin, register it in the main menu home delegate.
+     *
+     * @param plugin Data of the plugin.
+     * @param handlerName Name of the handler in the plugin.
+     * @param handlerSchema Data about the handler.
+     * @param initResult Result of the init WS call.
+     * @return A string to identify the handler.
+     */
+    protected registerMainMenuHomeHandler(
+        plugin: CoreSitePluginsPlugin,
+        handlerName: string,
+        handlerSchema: CoreSitePluginsMainMenuHomeHandlerData,
+        initResult: CoreSitePluginsContent | null,
+    ): string | undefined {
+        if (!handlerSchema.displaydata) {
+            // Required data not provided, stop.
+            this.logger.warn('Ignore site plugin because it doesn\'t provide displaydata', plugin, handlerSchema);
+
+            return;
+        }
+
+        this.logger.debug('Register site plugin in main menu home delegate:', plugin, handlerSchema, initResult);
+
+        // Create and register the handler.
+        const uniqueName = CoreSitePlugins.getHandlerUniqueName(plugin, handlerName);
+        const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title || 'pluginname');
+
+        CoreMainMenuHomeDelegate.registerHandler(
+            new CoreSitePluginsMainMenuHomeHandler(uniqueName, prefixedTitle, plugin, handlerSchema, initResult),
+        );
+
+        return uniqueName;
+    }
+
 }
 
 export const CoreSitePluginsHelper = makeSingleton(CoreSitePluginsHelperProvider);
diff --git a/src/core/features/siteplugins/services/siteplugins.ts b/src/core/features/siteplugins/services/siteplugins.ts
index 1110755ef..cbbfbea40 100644
--- a/src/core/features/siteplugins/services/siteplugins.ts
+++ b/src/core/features/siteplugins/services/siteplugins.ts
@@ -784,7 +784,7 @@ export type CoreSitePluginsPlugin = CoreSitePluginsWSPlugin & {
 export type CoreSitePluginsHandlerData = CoreSitePluginsInitHandlerData | CoreSitePluginsCourseOptionHandlerData |
 CoreSitePluginsMainMenuHandlerData | CoreSitePluginsCourseModuleHandlerData | CoreSitePluginsCourseFormatHandlerData |
 CoreSitePluginsUserHandlerData | CoreSitePluginsSettingsHandlerData | CoreSitePluginsMessageOutputHandlerData |
-CoreSitePluginsBlockHandlerData;
+CoreSitePluginsBlockHandlerData | CoreSitePluginsMainMenuHomeHandlerData;
 
 /**
  * Plugin handler data common to all delegates.
@@ -920,3 +920,15 @@ export type CoreSitePluginsInitHandlerData = CoreSitePluginsHandlerCommonData &
     methodJSResult?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
     methodOtherdata?: Record<string, unknown>;
 };
+
+/**
+ * Main menu home handler specific data.
+ */
+export type CoreSitePluginsMainMenuHomeHandlerData = CoreSitePluginsHandlerCommonData & {
+    displaydata?: {
+        title?: string;
+        class?: string;
+    };
+    priority?: number;
+    ptrenabled?: boolean;
+};
diff --git a/src/core/features/siteplugins/siteplugins.module.ts b/src/core/features/siteplugins/siteplugins.module.ts
index 154e3feb0..730d504e2 100644
--- a/src/core/features/siteplugins/siteplugins.module.ts
+++ b/src/core/features/siteplugins/siteplugins.module.ts
@@ -17,6 +17,7 @@ import { Routes } from '@angular/router';
 
 import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module';
 import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
+import { CoreMainMenuHomeRoutingModule } from '@features/mainmenu/pages/home/home-routing.module';
 import { CoreSitePluginsComponentsModule } from './components/components.module';
 import { CoreSitePluginsHelper } from './services/siteplugins-helper';
 
@@ -39,6 +40,7 @@ const courseIndexRoutes: Routes = [
     imports: [
         CoreMainMenuTabRoutingModule.forChild(routes),
         CoreCourseIndexRoutingModule.forChild({ children: courseIndexRoutes }),
+        CoreMainMenuHomeRoutingModule.forChild({ children: routes }),
         CoreSitePluginsComponentsModule,
     ],
     providers: [