diff --git a/package.json b/package.json index ad25ca2165..54d60b060e 100644 --- a/package.json +++ b/package.json @@ -42,9 +42,9 @@ "@angular/platform-browser-dynamic": "~17.2.2", "@angular/router": "~17.2.2", "@capacitor-community/file-opener": "^6.0.0", - "@capacitor-firebase/authentication": "^6.1.0", - "@capacitor-firebase/crashlytics": "^6.1.0", - "@capacitor-firebase/performance": "^6.1.0", + "@capacitor-firebase/authentication": "^6.3.1", + "@capacitor-firebase/crashlytics": "^6.3.1", + "@capacitor-firebase/performance": "^6.3.1", "@capacitor/android": "^6.0.0", "@capacitor/app": "^6.0.0", "@capacitor/clipboard": "^6.0.0", diff --git a/packages/data-models/appConfig.ts b/packages/data-models/appConfig.ts index d296c9fb70..d34c6cb1e3 100644 --- a/packages/data-models/appConfig.ts +++ b/packages/data-models/appConfig.ts @@ -83,24 +83,33 @@ const APP_ROUTE_DEFAULTS = { ], }; -export type IHeaderColourOptions = "primary" | "secondary" | "none"; +export type IHeaderFooterBackgroundOptions = "primary" | "secondary" | "none"; +/** The "compact" variant reduces the header height and removes the title */ export type IHeaderVariantOptions = "default" | "compact"; -const APP_HEADER_DEFAULTS = { - title: "App", +interface IAppConfigHeader { + back_button: { + hidden?: boolean; + }; + collapse: boolean; + colour: IHeaderFooterBackgroundOptions; + hidden?: boolean; + menu_button: { + hidden?: boolean; + }; + template: string | null; + title: string; + variant: IHeaderVariantOptions; +} + +const APP_HEADER_DEFAULTS: IAppConfigHeader = { + back_button: {}, collapse: false, - colour: "primary" as IHeaderColourOptions, - // The "compact" variant reduces the header height and removes the title - variant: "default" as IHeaderVariantOptions, - // default only show menu button on home screen - should_show_menu_button: (location: Location) => - activeRoute(location) === APP_ROUTE_DEFAULTS.home_route, - // default show back button on all screens except home screen - should_show_back_button: (location: Location) => - activeRoute(location) !== APP_ROUTE_DEFAULTS.home_route, - // on device minimize app when back button pressed from home screen - should_minimize_app_on_back: (location: Location) => - activeRoute(location) === APP_ROUTE_DEFAULTS.home_route, + colour: "primary", + menu_button: {}, + template: null, + title: "App", + variant: "default", }; /** @@ -113,8 +122,9 @@ const activeRoute = (location: Location) => { return path; }; -const APP_FOOTER_DEFAULTS: { templateName: string | null } = { - templateName: null, +const APP_FOOTER_DEFAULTS = { + templateName: null as string | null, + background: "primary" as IHeaderFooterBackgroundOptions, }; const LAYOUT = { @@ -132,10 +142,11 @@ const APP_SIDEMENU_DEFAULTS = { should_show_deployment_name: false, }; -const APP_AUTHENTICATION_DEFAULTS = { - enforceLogin: false, - signInTemplate: "sign_in", -}; +/** + * @deprecated 0.18.0 + * Use `deployment.auth` to configure auth + */ +const APP_AUTHENTICATION_DEFAULTS = {}; type IAppLaunchAction = { type: "template_popup" | "tour_start"; diff --git a/packages/data-models/deployment.model.ts b/packages/data-models/deployment.model.ts index f884873b8b..776b3771ee 100644 --- a/packages/data-models/deployment.model.ts +++ b/packages/data-models/deployment.model.ts @@ -3,7 +3,7 @@ import type { IGdriveEntry } from "../@idemsInternational/gdrive-tools"; import type { IAppConfig, IAppConfigOverride } from "./appConfig"; /** Update version to force recompile next time deployment set (e.g. after default config update) */ -export const DEPLOYMENT_CONFIG_VERSION = 20241111.0; +export const DEPLOYMENT_CONFIG_VERSION = 20241215.1; /** Configuration settings available to runtime application */ export interface IDeploymentRuntimeConfig { @@ -36,6 +36,13 @@ export interface IDeploymentRuntimeConfig { /** sentry/glitchtip logging dsn */ dsn: string; }; + /** Enable auth actions by specifying auth provider */ + auth: { + /** provider to use with authentication actions. actions will be disabled if no provider specified */ + provider?: "firebase" | "supabase"; + /** prevent user accessing app pages without being logged in. Specified template will be shown until logged in */ + enforceLoginTemplate?: string; + }; /** * Specify if using firebase for auth and crashlytics. * Requires firebase config available through encrypted config */ @@ -51,10 +58,6 @@ export interface IDeploymentRuntimeConfig { appId: string; measurementId: string; }; - auth: { - /** Enables `auth` actions to allow user sign-in/out */ - enabled: boolean; - }; crashlytics: { /** Enables app crash reports to firebase crashlytics */ enabled: boolean; @@ -200,9 +203,9 @@ export const DEPLOYMENT_RUNTIME_CONFIG_DEFAULTS: IDeploymentRuntimeConfig = { endpoint: "https://apps-server.idems.international/analytics", }, app_config: {}, + auth: {}, firebase: { config: null, - auth: { enabled: false }, crashlytics: { enabled: true }, }, supabase: { diff --git a/packages/data-models/skin.model.ts b/packages/data-models/skin.model.ts index d6c627b925..163c7b4886 100644 --- a/packages/data-models/skin.model.ts +++ b/packages/data-models/skin.model.ts @@ -12,7 +12,9 @@ import { IAppConfigOverride } from "./appConfig"; * enabled: false * } * APP_HEADER_DEFAULTS: { - * should_show_menu_button: false + * menu_button: { + * hidden: true + * } * } * } * } diff --git a/packages/scripts/src/tasks/providers/appData.ts b/packages/scripts/src/tasks/providers/appData.ts index 94e9277c1c..3e4ba0b09c 100644 --- a/packages/scripts/src/tasks/providers/appData.ts +++ b/packages/scripts/src/tasks/providers/appData.ts @@ -68,7 +68,7 @@ const copyDeploymentDataToApp = async () => { const optimiseBuild = async () => new AppDataOptimiser(WorkflowRunner.config).run(); function generateRuntimeConfig(deploymentConfig: IDeploymentConfigJson): IDeploymentRuntimeConfig { - const { analytics, api, app_config, error_logging, firebase, git, name, supabase, web } = + const { analytics, api, app_config, auth, error_logging, firebase, git, name, supabase, web } = deploymentConfig; return { @@ -77,6 +77,7 @@ function generateRuntimeConfig(deploymentConfig: IDeploymentConfigJson): IDeploy analytics, api, app_config, + auth, error_logging, firebase, name, diff --git a/src/app/app.component.html b/src/app/app.component.html index 1a4ee62ac7..2ccfeedfb5 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -5,13 +5,13 @@ [dir]="templateTranslateService.languageDirection()" > - @if (sideMenuDefaults().enabled) { + @if (sideMenuConfig().enabled) { - {{ sideMenuDefaults().title }} + {{ sideMenuConfig().title }}
- @if (sideMenuDefaults().should_show_version) { + @if (sideMenuConfig().should_show_version) { @if (deploymentConfig._content_version; as CONTENT_VERSION) { @@ -22,7 +22,7 @@ {{ deploymentConfig._app_builder_version }} } } - @if (sideMenuDefaults().should_show_deployment_name) { + @if (sideMenuConfig().should_show_deployment_name) { ({{ deploymentConfig.name }}) }
@@ -30,7 +30,7 @@
@@ -41,13 +41,15 @@
- +
- @if (footerDefaults().templateName; as footerTemplateName) { + @if (footerConfig().templateName; as footerTemplateName) { - + this.appConfigService.appConfig().APP_SIDEMENU_DEFAULTS); - footerDefaults = computed(() => this.appConfigService.appConfig().APP_FOOTER_DEFAULTS); + headerConfig = computed(() => this.appConfigService.appConfig().APP_HEADER_DEFAULTS); + footerConfig = computed(() => this.appConfigService.appConfig().APP_FOOTER_DEFAULTS); + sideMenuConfig = computed(() => this.appConfigService.appConfig().APP_SIDEMENU_DEFAULTS); layoutConfig = computed(() => this.appConfigService.appConfig().LAYOUT); public routeContainerStyle = computed(() => { @@ -109,7 +110,6 @@ export class AppComponent { public templateTranslateService: TemplateTranslateService, private crashlyticsService: CrashlyticsService, private appDataService: AppDataService, - private authService: AuthService, private seoService: SeoService, private taskService: TaskService, private feedbackService: FeedbackService, @@ -134,7 +134,6 @@ export class AppComponent { this.hackSetDeveloperOptions(); const isDeveloperMode = this.templateFieldService.getField("user_mode") === false; const user = this.userMetaService.userMeta; - await this.loadAuthConfig(); if (!user.first_app_open) { await this.userMetaService.setUserMeta({ first_app_open: new Date().toISOString() }); @@ -173,29 +172,6 @@ export class AppComponent { } } - /** - * Authentication requires verified domain and app ids populated to firebase console - * Currently only run on native where specified (but can comment out for testing locally) - */ - private async loadAuthConfig() { - const { firebase } = this.deploymentService.config; - const { enforceLogin, signInTemplate } = - this.appConfigService.appConfig().APP_AUTHENTICATION_DEFAULTS; - const ensureLogin = firebase.config && enforceLogin && Capacitor.isNativePlatform(); - if (ensureLogin) { - this.authService.ready(); - const authUser = await this.authService.getCurrentUser(); - if (!authUser) { - const { modal } = await this.templateService.runStandaloneTemplate(signInTemplate, { - showCloseButton: false, - waitForDismiss: false, - }); - await this.authService.waitForSignInComplete(); - await modal.dismiss(); - } - } - } - /** * Various services set core app data which may be used in templates such as current app day, * user id etc. Make sure these services have run their initialisation logic before proceeding. @@ -240,7 +216,6 @@ export class AppComponent { this.templateService, this.templateProcessService, this.appDataService, - this.authService, this.serverService, this.seoService, this.feedbackService, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 591b129364..aedaf10727 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -38,16 +38,16 @@ export function lottiePlayerFactory() { BrowserAnimationsModule, IonicModule.forRoot(), AppRoutingModule, + TemplateComponentsModule, + DeploymentFeaturesModule, HttpClientModule, SharedModule, FormsModule, LottieModule.forRoot({ player: lottiePlayerFactory }), // NOTE CC 2021-11-04 not sure if cache causes issues or not https://github.com/ngx-lottie/ngx-lottie/issues/115 // LottieCacheModule.forRoot(), - TemplateComponentsModule, TourModule, ContextMenuModule, - DeploymentFeaturesModule, ], providers: [ { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, diff --git a/src/app/deployment-features.module.ts b/src/app/deployment-features.module.ts index e5c44bfdb8..8c12edc419 100644 --- a/src/app/deployment-features.module.ts +++ b/src/app/deployment-features.module.ts @@ -2,6 +2,7 @@ import { NgModule } from "@angular/core"; import { AnalyticsModule } from "./shared/services/analytics"; import { NavStackModule } from "./feature/nav-stack/nav-stack.module"; +import { AuthModule } from "./shared/services/auth/auth.module"; /** * Module imports required for specific deployment features @@ -14,5 +15,5 @@ import { NavStackModule } from "./feature/nav-stack/nav-stack.module"; * * This is a feature marked for future implementation */ -@NgModule({ imports: [AnalyticsModule, NavStackModule] }) +@NgModule({ imports: [AuthModule, AnalyticsModule, NavStackModule] }) export class DeploymentFeaturesModule {} diff --git a/src/app/shared/components/header/header.component.html b/src/app/shared/components/header/header.component.html index a66a230875..6c03f2b83a 100644 --- a/src/app/shared/components/header/header.component.html +++ b/src/app/shared/components/header/header.component.html @@ -14,15 +14,16 @@ - - + @if (headerConfig().template) { + + } @else if (headerConfig().title && headerConfig().variant !== "compact") { + {{ headerConfig().title }} - + } diff --git a/src/app/shared/components/header/header.component.ts b/src/app/shared/components/header/header.component.ts index 5ce984e9f1..ca6e197975 100644 --- a/src/app/shared/components/header/header.component.ts +++ b/src/app/shared/components/header/header.component.ts @@ -2,7 +2,7 @@ import { Location } from "@angular/common"; import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { computed, effect, signal } from "@angular/core"; import { toSignal } from "@angular/core/rxjs-interop"; -import { NavigationEnd, NavigationStart, Router } from "@angular/router"; +import { NavigationStart, Router } from "@angular/router"; import { App } from "@capacitor/app"; import { Capacitor, PluginListenerHandle } from "@capacitor/core"; import { Subscription, fromEvent, map } from "rxjs"; @@ -10,6 +10,7 @@ import type { IHeaderVariantOptions } from "data-models/appConfig"; import { AppConfigService } from "../../services/app-config/app-config.service"; import { IonHeader, ScrollBaseCustomEvent, ScrollDetail } from "@ionic/angular"; import { _wait } from "packages/shared/src/utils/async-utils"; +import { activeRoute } from "../../utils/angular.utils"; interface ScrollCustomEvent extends ScrollBaseCustomEvent { detail: ScrollDetail; @@ -51,6 +52,9 @@ export class headerComponent implements OnInit, OnDestroy { /** Track scroll events when using header collapse mode */ private scrollEvents$: Subscription; + /** Track whether on home route for back button and menu button side-effects */ + private isHomeRoute = true; + constructor( private router: Router, private location: Location, @@ -71,9 +75,7 @@ export class headerComponent implements OnInit, OnDestroy { () => { // when route changes handle side-effects const e = this.routeChanges(); - if (e instanceof NavigationEnd) { - this.handleRouteChange(); - } + this.handleRouteChange(); if (e instanceof NavigationStart) { this.hasBackHistory = true; } @@ -103,16 +105,24 @@ export class headerComponent implements OnInit, OnDestroy { /** Determine whether to show back and menu buttons based on location */ private handleRouteChange() { - const { should_show_back_button, should_show_menu_button } = this.headerConfig(); - this.showBackButton.set(should_show_back_button(location)); - this.showMenuButton.set(should_show_menu_button(location)); + // update whether home route or not + const { APP_ROUTE_DEFAULTS } = this.appConfigService.appConfig(); + this.isHomeRoute = activeRoute(location) === APP_ROUTE_DEFAULTS.home_route; + + const { back_button, menu_button } = this.headerConfig(); + + // The explicit `hidden` property should override the function of location + // TODO: move functions to component code and out of app config, no use case for overriding them + const showBackButton = !back_button.hidden && !this.isHomeRoute; + const showMenuButton = !menu_button.hidden && this.isHomeRoute; + this.showBackButton.set(showBackButton); + this.showMenuButton.set(showMenuButton); this.marginTop.set(0); } /** When device back button evaluate conditions to handle app minimise */ private handleHardwareBackPress() { - const { should_minimize_app_on_back } = this.headerConfig(); - if (should_minimize_app_on_back(location)) { + if (this.isHomeRoute) { App.minimizeApp(); } } diff --git a/src/app/shared/components/template/components/combo-box/combo-box-modal/combo-box-modal.component.html b/src/app/shared/components/template/components/combo-box/combo-box-modal/combo-box-modal.component.html index 397245aca7..6c6084e384 100644 --- a/src/app/shared/components/template/components/combo-box/combo-box-modal/combo-box-modal.component.html +++ b/src/app/shared/components/template/components/combo-box/combo-box-modal/combo-box-modal.component.html @@ -8,7 +8,7 @@