From 2c38c1683daa299e58288ccaedf60ae3fc79964f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= <crazyserver@gmail.com>
Date: Mon, 23 Nov 2020 12:30:30 +0100
Subject: [PATCH] MOBILE-3565 core: Apply status bar color changes

---
 config/config.json            |   8 ---
 src/app/app.component.ts      |   3 +
 src/core/services/app.ts      |  51 +++++++--------
 src/core/singletons/colors.ts | 117 ++++++++++++++++++++++++++++++++++
 src/theme/variables.scss      |   3 +-
 src/types/global.d.ts         |   8 ---
 6 files changed, 144 insertions(+), 46 deletions(-)
 create mode 100644 src/core/singletons/colors.ts

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/src/app/app.component.ts b/src/app/app.component.ts
index 0135f049a..91c79bbaf 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -146,6 +146,9 @@ export class AppComponent implements OnInit {
                 }
             });
         });
+
+        // Set StatusBar properties.
+        CoreApp.instance.setStatusBarColor();
     }
 
     /**
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/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/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;