diff --git a/config.xml b/config.xml
index 6c14fa033..66c859986 100644
--- a/config.xml
+++ b/config.xml
@@ -31,6 +31,7 @@
+
diff --git a/config/config.json b/config/config.json
index 601928dfa..ee5a78a74 100644
--- a/config/config.json
+++ b/config/config.json
@@ -86,14 +86,6 @@
"forcedefaultlanguage": false,
"privacypolicy": "https:\/\/moodle.net\/moodle-app-privacy\/",
"notificoncolor": "#f98012",
- "statusbarbg": false,
- "statusbarlighttext": false,
- "statusbarbgios": "#f98012",
- "statusbarlighttextios": true,
- "statusbarbgandroid": "#df7310",
- "statusbarlighttextandroid": true,
- "statusbarbgremotetheme": "#000000",
- "statusbarlighttextremotetheme": true,
"enableanalytics": false,
"enableonboarding": true,
"forceColorScheme": "",
diff --git a/package-lock.json b/package-lock.json
index 229012d24..cb5a21026 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6522,7 +6522,7 @@
"integrity": "sha512-EYC5eQFVkoYXq39l7tYKE6lEjHJ04mvTmKXxGL7quHLdFPfJMNzru/UYpn92AOfpl3PQaZmou78C7EgmFOwFQQ=="
},
"cordova-plugin-wkuserscript": {
- "version": "git+https://github.com/moodlemobile/cordova-plugin-wkuserscript.git#6413f4bb3c2565f353e690b5c1450b69ad9e860e",
+ "version": "git+https://github.com/moodlemobile/cordova-plugin-wkuserscript.git#aa77d0f98a3fb106f2e798e5adf5882f01a2c947",
"from": "git+https://github.com/moodlemobile/cordova-plugin-wkuserscript.git"
},
"cordova-plugin-wkwebview-cookies": {
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 1bd3a63de..91c79bbaf 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -17,9 +17,16 @@ import { NavController } from '@ionic/angular';
import { CoreLangProvider } from '@services/lang';
import { CoreLoginHelperProvider } from '@features/login/services/login.helper';
-import { CoreEvents, CoreEventSessionExpiredData } from '@singletons/events';
+import {
+ CoreEvents,
+ CoreEventSessionExpiredData,
+ CoreEventSiteAddedData,
+ CoreEventSiteData,
+ CoreEventSiteUpdatedData,
+} from '@singletons/events';
import { Network, NgZone, Platform } from '@singletons/core.singletons';
import { CoreApp } from '@services/app';
+import { CoreSites } from '@services/sites';
@Component({
selector: 'app-root',
@@ -37,6 +44,14 @@ export class AppComponent implements OnInit {
/**
* Component being initialized.
+ *
+ * @todo Review all old code to see if something is missing:
+ * - IAB events listening.
+ * - Platform pause/resume subscriptions.
+ * - handleOpenURL and openWindowSafely.
+ * - Screen orientation events (probably it can be removed).
+ * - Back button registering to close modal first.
+ * - Note: HideKeyboardFormAccessoryBar has been moved to config.xml.
*/
ngOnInit(): void {
CoreEvents.on(CoreEvents.LOGOUT, () => {
@@ -55,9 +70,59 @@ export class AppComponent implements OnInit {
this.loginHelper.sessionExpired(data);
});
+ // Listen for passwordchange and usernotfullysetup events to open InAppBrowser.
+ CoreEvents.on(CoreEvents.PASSWORD_CHANGE_FORCED, (data: CoreEventSiteData) => {
+ this.loginHelper.passwordChangeForced(data.siteId!);
+ });
+ CoreEvents.on(CoreEvents.USER_NOT_FULLY_SETUP, (data: CoreEventSiteData) => {
+ this.loginHelper.openInAppForEdit(data.siteId!, '/user/edit.php', 'core.usernotfullysetup');
+ });
+
+ // Listen for sitepolicynotagreed event to accept the site policy.
+ CoreEvents.on(CoreEvents.SITE_POLICY_NOT_AGREED, (data: CoreEventSiteData) => {
+ this.loginHelper.sitePolicyNotAgreed(data.siteId);
+ });
+
+ CoreEvents.on(CoreEvents.LOGIN, async (data: CoreEventSiteData) => {
+ if (data.siteId) {
+ const site = await CoreSites.instance.getSite(data.siteId);
+ const info = site.getInfo();
+ if (info) {
+ // Add version classes to body.
+ this.removeVersionClass();
+ this.addVersionClass(CoreSites.instance.getReleaseNumber(info.release || ''));
+ }
+ }
+
+ this.loadCustomStrings();
+ });
+
+ CoreEvents.on(CoreEvents.SITE_UPDATED, (data: CoreEventSiteUpdatedData) => {
+ if (data.siteId == CoreSites.instance.getCurrentSiteId()) {
+ this.loadCustomStrings();
+
+ // Add version classes to body.
+ this.removeVersionClass();
+ this.addVersionClass(CoreSites.instance.getReleaseNumber(data.release || ''));
+ }
+ });
+
+ CoreEvents.on(CoreEvents.SITE_ADDED, (data: CoreEventSiteAddedData) => {
+ if (data.siteId == CoreSites.instance.getCurrentSiteId()) {
+ this.loadCustomStrings();
+
+ // Add version classes to body.
+ this.removeVersionClass();
+ this.addVersionClass(CoreSites.instance.getReleaseNumber(data.release || ''));
+ }
+ });
+
this.onPlatformReady();
}
+ /**
+ * Async init function on platform ready.
+ */
protected async onPlatformReady(): Promise {
await Platform.instance.ready();
@@ -81,6 +146,19 @@ export class AppComponent implements OnInit {
}
});
});
+
+ // Set StatusBar properties.
+ CoreApp.instance.setStatusBarColor();
+ }
+
+ /**
+ * Load custom lang strings. This cannot be done inside the lang provider because it causes circular dependencies.
+ */
+ protected loadCustomStrings(): void {
+ const currentSite = CoreSites.instance.getCurrentSite();
+ if (currentSite) {
+ this.langProvider.loadCustomStringsFromSite(currentSite);
+ }
}
/**
diff --git a/src/core/features/search/search.module.ts b/src/core/features/search/search.module.ts
index a59da3976..89ba8527e 100644
--- a/src/core/features/search/search.module.ts
+++ b/src/core/features/search/search.module.ts
@@ -20,13 +20,10 @@ import { CoreSearchComponentsModule } from './components/components.module';
import { SITE_SCHEMA } from './services/search-history-db';
@NgModule({
- declarations: [
- ],
imports: [
CoreSearchComponentsModule,
],
providers: [
- CoreSearchComponentsModule,
{ provide: CORE_SITE_SCHEMAS, useValue: [SITE_SCHEMA], multi: true },
],
})
diff --git a/src/core/services/app.ts b/src/core/services/app.ts
index a09eea202..6d91795c4 100644
--- a/src/core/services/app.ts
+++ b/src/core/services/app.ts
@@ -26,6 +26,7 @@ import { CoreConstants } from '@/core/constants';
import { makeSingleton, Keyboard, Network, StatusBar, Platform, Device } from '@singletons/core.singletons';
import { CoreLogger } from '@singletons/logger';
import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/app.db';
+import { CoreColors } from '../singletons/colors';
/**
* Factory to provide some global functionalities, like access to the global app database.
@@ -611,43 +612,35 @@ export class CoreAppProvider {
/**
* Set StatusBar color depending on platform.
+ *
+ * @param color RGB color to use as status bar background. If not set the css variable will be read.
*/
- setStatusBarColor(): void {
- if (typeof CoreConstants.CONFIG.statusbarbgios == 'string' && this.isIOS()) {
- // IOS Status bar properties.
- StatusBar.instance.overlaysWebView(false);
- StatusBar.instance.backgroundColorByHexString(CoreConstants.CONFIG.statusbarbgios);
- CoreConstants.CONFIG.statusbarlighttextios ? StatusBar.instance.styleLightContent() : StatusBar.instance.styleDefault();
- } else if (typeof CoreConstants.CONFIG.statusbarbgandroid == 'string' && this.isAndroid()) {
- // Android Status bar properties.
- StatusBar.instance.backgroundColorByHexString(CoreConstants.CONFIG.statusbarbgandroid);
- CoreConstants.CONFIG.statusbarlighttextandroid ?
- StatusBar.instance.styleLightContent() : StatusBar.instance.styleDefault();
- } else if (typeof CoreConstants.CONFIG.statusbarbg == 'string') {
- // Generic Status bar properties.
- this.isIOS() && StatusBar.instance.overlaysWebView(false);
- StatusBar.instance.backgroundColorByHexString(CoreConstants.CONFIG.statusbarbg);
- CoreConstants.CONFIG.statusbarlighttext ? StatusBar.instance.styleLightContent() : StatusBar.instance.styleDefault();
- } else {
- // Default Status bar properties.
- this.isAndroid() ? StatusBar.instance.styleLightContent() : StatusBar.instance.styleDefault();
+ setStatusBarColor(color?: string): void {
+ if (!color) {
+ // Get the default color to reset it.
+ color = getComputedStyle(document.documentElement).getPropertyValue('--ion-statusbar-background').trim();
}
+
+ // Make darker on Android.
+ if (this.isAndroid()) {
+ color = CoreColors.darker(color);
+ }
+
+ const useLightText = CoreColors.isWhiteContrastingBetter(color);
+ const statusBar = StatusBar.instance;
+ statusBar.backgroundColorByHexString(color);
+ useLightText ? statusBar.styleLightContent() : statusBar.styleDefault();
+
+ this.isIOS() && statusBar.overlaysWebView(false);
}
/**
* Reset StatusBar color if any was set.
+ *
+ * @deprecated Use setStatusBarColor passing the color of the new statusbar color loaded on remote theme or no color to reset.
*/
resetStatusBarColor(): void {
- if (typeof CoreConstants.CONFIG.statusbarbgremotetheme == 'string' &&
- ((typeof CoreConstants.CONFIG.statusbarbgios == 'string' && this.isIOS()) ||
- (typeof CoreConstants.CONFIG.statusbarbgandroid == 'string' && this.isAndroid()) ||
- typeof CoreConstants.CONFIG.statusbarbg == 'string')) {
- // If the status bar has been overriden and there's a fallback color for remote themes, use it now.
- this.isIOS() && StatusBar.instance.overlaysWebView(false);
- StatusBar.instance.backgroundColorByHexString(CoreConstants.CONFIG.statusbarbgremotetheme);
- CoreConstants.CONFIG.statusbarlighttextremotetheme ?
- StatusBar.instance.styleLightContent() : StatusBar.instance.styleDefault();
- }
+ this.setStatusBarColor();
}
/**
diff --git a/src/core/services/lang.ts b/src/core/services/lang.ts
index e1e0d6879..e4663d0a7 100644
--- a/src/core/services/lang.ts
+++ b/src/core/services/lang.ts
@@ -21,6 +21,7 @@ import { CoreConfig } from '@services/config';
import { makeSingleton, Translate, Platform } from '@singletons/core.singletons';
import * as moment from 'moment';
+import { CoreSite } from '../classes/site';
/*
* Service to handle language features, like changing the current language.
@@ -311,6 +312,19 @@ export class CoreLangProvider {
});
}
+ /**
+ * Loads custom strings obtained from site.
+ *
+ * @param currentSite Current site object.
+ */
+ loadCustomStringsFromSite(currentSite: CoreSite): void {
+ const customStrings = currentSite.getStoredConfig('tool_mobile_customlangstrings');
+
+ if (typeof customStrings != 'undefined') {
+ this.loadCustomStrings(customStrings);
+ }
+ }
+
/**
* Load certain custom strings.
*
diff --git a/src/core/singletons/colors.ts b/src/core/singletons/colors.ts
new file mode 100644
index 000000000..c8cc5cce9
--- /dev/null
+++ b/src/core/singletons/colors.ts
@@ -0,0 +1,117 @@
+// (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.
+
+/**
+ * Color components contained within a rgb color.
+ */
+interface ColorComponents {
+ red: number; // Red component of an RGB color [0-255].
+ green: number; // Green component of an RGB color [0-255].
+ blue: number; // Blue component of an RGB color [0-255].
+}
+
+/**
+ * Singleton with helper functions for colors.
+ */
+export class CoreColors {
+
+ /**
+ * Returns better contrast color.
+ *
+ * @param color Black or white texts.
+ * @return True if white contrasts better than black. False otherwise.
+ */
+ static isWhiteContrastingBetter(color: string): boolean {
+ return CoreColors.luma(color) < 165;
+ }
+
+ /**
+ * Returns the same color 10% darker to be used as status bar on Android.
+ *
+ * @param color Color to get darker.
+ * @return Darker Hex RGB color.
+ */
+ static darker(color: string, percent: number = 10): string {
+ percent = 1 - (percent / 100);
+ const components = CoreColors.hexToRGB(color);
+ components.red = Math.floor(components.red * percent) ;
+ components.green = Math.floor(components.green * percent) ;
+ components.blue = Math.floor(components.blue * percent) ;
+
+ return CoreColors.RGBToHex(components);
+ }
+
+ /**
+ * Gets the luma of a color.
+ *
+ * @param color Hex RGB color.
+ * @return Luma number based on SMPTE C, Rec. 709 weightings.
+ */
+ protected static luma(color: string): number {
+ const rgb = CoreColors.hexToRGB(color);
+
+ return (rgb.red * 0.2126) + (rgb.green * 0.7152) + (rgb.blue * 0.0722);
+ }
+
+ /**
+ * Converts Hex RGB to Color components.
+ *
+ * @param color Hexadec RGB Color.
+ * @return RGB color components.
+ */
+ protected static hexToRGB(color: string): ColorComponents {
+ if (color.charAt(0) == '#') {
+ color = color.substr(1);
+ }
+
+ if (color.length === 3) {
+ color = color.charAt(0) + color.charAt(0) + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2);
+ } else if (color.length !== 6) {
+ throw('Invalid hex color: ' + color);
+ }
+
+ return {
+ red: parseInt(color.substr(0, 2), 16),
+ green: parseInt(color.substr(2, 2), 16),
+ blue: parseInt(color.substr(4, 2), 16),
+ };
+
+ }
+
+ /**
+ * Converts RGB components to Hex string.
+ *
+ * @param color Color components.
+ * @return RGB color in string.
+ */
+ protected static RGBToHex(color: ColorComponents): string {
+ return '#' + CoreColors.componentToHex(color.red) +
+ CoreColors.componentToHex(color.green) +
+ CoreColors.componentToHex(color.blue);
+
+ }
+
+ /**
+ * Converts a color component from decimal to hexadec.
+ *
+ * @param c color component in decimal.
+ * @return Hexadec of the color component.
+ */
+ protected static componentToHex(c: number): string {
+ const hex = c.toString(16);
+
+ return hex.length == 1 ? '0' + hex : hex;
+ }
+
+}
diff --git a/src/core/singletons/events.ts b/src/core/singletons/events.ts
index e58dc9740..f7033acd0 100644
--- a/src/core/singletons/events.ts
+++ b/src/core/singletons/events.ts
@@ -205,6 +205,11 @@ export type CoreEventSiteData = {
*/
export type CoreEventSiteUpdatedData = CoreEventSiteData & CoreSiteInfoResponse;
+/**
+ * Data passed to SITE_ADDED event.
+ */
+export type CoreEventSiteAddedData = CoreEventSiteData & CoreSiteInfoResponse;
+
/**
* Data passed to SESSION_EXPIRED event.
*/
diff --git a/src/theme/variables.scss b/src/theme/variables.scss
index e35b04f3c..aeb85e89e 100644
--- a/src/theme/variables.scss
+++ b/src/theme/variables.scss
@@ -100,9 +100,10 @@
--color: var(--custom-bottom-tabs-color, var(--white));
}
+ --ion-statusbar-background: var(--custom-toolbar-background, var(--ion-color-primary));
ion-toolbar {
--color: var(--custom-toolbar-color, var(--ion-color-primary-contrast));
- --background: var(--custom-toolbar-background, var(--ion-color-primary));
+ --background: var(--ion-statusbar-background);
ion-spinner {
--color: var(--custom-toolbar-color, var(--ion-color-primary-contrast));
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
index 9d4eedb56..21f818810 100644
--- a/src/types/global.d.ts
+++ b/src/types/global.d.ts
@@ -50,14 +50,6 @@ declare global {
forcedefaultlanguage: boolean;
privacypolicy: string;
notificoncolor: string;
- statusbarbg: boolean;
- statusbarlighttext: boolean;
- statusbarbgios: string;
- statusbarlighttextios: boolean;
- statusbarbgandroid: string;
- statusbarlighttextandroid: boolean;
- statusbarbgremotetheme: string;
- statusbarlighttextremotetheme: boolean;
enableanalytics: boolean;
enableonboarding: boolean;
forceColorScheme: CoreColorScheme;