Skip to content

Commit

Permalink
MOBILE-4331 app: Change navigation bar color according to the theme.
Browse files Browse the repository at this point in the history
  • Loading branch information
crazyserver committed Jul 25, 2023
1 parent 33cda28 commit ef8a8c5
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 31 deletions.
6 changes: 5 additions & 1 deletion cordova-plugin-moodleapp/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
<js-module src="www/index.js" name="moodleapp">
<clobbers target="cordova.MoodleApp" />
</js-module>

<platform name="android">
<source-file src="src/android/SystemUI.java" target-dir="src/com/moodle/moodlemobile" />

<config-file target="res/xml/config.xml" parent="/*">
<feature name="SystemUI">
<param name="android-package" value="com.moodle.moodlemobile.SystemUI"/>
<param name="onload" value="true" />
</feature>
</config-file>
<source-file src="src/android/SystemUI.java" target-dir="src/com/moodle/moodlemobile" />
</platform>
</platform>
</plugin>
130 changes: 120 additions & 10 deletions cordova-plugin-moodleapp/src/android/SystemUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,48 @@
import android.os.Build;
import android.util.Log;
import android.view.Window;
import android.view.WindowManager;

import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaInterface;

import android.view.View;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsControllerCompat;

import org.json.JSONArray;

public class SystemUI extends CordovaPlugin {

private static final String TAG = "SystemUI";

/**
* Sets the context of the Command. This can then be used to do things like
* get file paths associated with the Activity.
*
* @param cordova The context of the main Activity.
* @param webView The CordovaWebView Cordova is running in.
*/
@Override
public void initialize(final CordovaInterface cordova, CordovaWebView webView) {
Log.v(TAG, "Init");
super.initialize(cordova, webView);

this.cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Clear flag FLAG_FORCE_NOT_FULLSCREEN which is set initially by the Cordova.
Window window = cordova.getActivity().getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);

setNavigationBarColor(preferences.getString("NavigationBarBackgroundColor", "#000000"));
setStatusBarColor(preferences.getString("StatusBarBackgroundColor", "#ffffff"));
}
});
}

@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
try {
Expand All @@ -36,6 +68,11 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo
this.setNavigationBarColor(args.getString(0));
callbackContext.success();

return true;
case "setStatusBarColor":
this.setStatusBarColor(args.getString(0));
callbackContext.success();

return true;
}
} catch (Throwable e) {
Expand All @@ -46,28 +83,43 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo
}

private void setNavigationBarColor(String color) {
if (Build.VERSION.SDK_INT < 21) {
if (Build.VERSION.SDK_INT < 26) {
return;
}

if (color == null || color.isEmpty()) {
return;
}

Log.d(TAG, "Setting navigation bar color to " + color);
final boolean useLightForeground = this.isLightModeNeeded(color);

Log.d(TAG, "Setting navigation bar color to " + color + " foreground " + (useLightForeground ? "light" : "dark"));

this.cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;
final int SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR = 0x00000010;
final Window window = cordova.getActivity().getWindow();
int uiOptions = window.getDecorView().getSystemUiVisibility();

uiOptions = uiOptions | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
uiOptions = uiOptions & ~SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;

window.getDecorView().setSystemUiVisibility(uiOptions);
final View decorView = window.getDecorView();
WindowInsetsControllerCompat windowInsetController = WindowCompat.getInsetsController(window, decorView);

if (windowInsetController == null) {
windowInsetController.setAppearanceLightNavigationBars(!useLightForeground);
} else {
// Deprectated in SDK 30.
final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;
final int SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR = 0x00000010;
int uiOptions = decorView.getSystemUiVisibility();

uiOptions = uiOptions | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
uiOptions = uiOptions & ~SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
if (useLightForeground) {
uiOptions = uiOptions | SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
} else {
uiOptions = uiOptions & ~SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
}

decorView.setSystemUiVisibility(uiOptions);
}

try {
// Using reflection makes sure any 5.0+ device will work without having to compile with SDK level 21
Expand All @@ -82,4 +134,62 @@ public void run() {
});
}

private void setStatusBarColor(String color) {
if (Build.VERSION.SDK_INT < 21) {
return;
}

if (color == null || color.isEmpty()) {
return;
}

final boolean useLightForeground = this.isLightModeNeeded(color);

Log.d(TAG, "Setting status bar color to " + color + " foreground " + (useLightForeground ? "light" : "dark"));

this.cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
final Window window = cordova.getActivity().getWindow();
final int FLAG_TRANSLUCENT_STATUS = 0x04000000; // SDK 19: WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000; // SDK 21: WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
// Method and constants not available on all SDKs but we want to be able to compile this code with any SDK
window.clearFlags(FLAG_TRANSLUCENT_STATUS);
window.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);

final View decorView = window.getDecorView();
WindowInsetsControllerCompat windowInsetController = WindowCompat.getInsetsController(window, decorView);

if (windowInsetController != null) {
windowInsetController.setAppearanceLightStatusBars(!useLightForeground);
} else {
// Deprectated in SDK 30.
int uiOptions = decorView.getSystemUiVisibility();

if (useLightForeground) {
uiOptions = uiOptions & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else {
uiOptions = uiOptions | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}

decorView.setSystemUiVisibility(uiOptions);
}

try {
// Using reflection makes sure any 5.0+ device will work without having to compile with SDK level 21
window.getClass().getMethod("setStatusBarColor", int.class).invoke(window, Color.parseColor(color));
} catch (IllegalArgumentException ignore) {
Log.e(TAG, "Invalid hexString argument, use f.i. '#999999'");
} catch (Exception ignore) {
// this should not happen, only in case Android removes this method in a version > 21
Log.w(TAG, "Method window.setStatusBarColor not found for SDK level " + Build.VERSION.SDK_INT);
}
}
});
}

private boolean isLightModeNeeded(String color) {
return Color.luminance(Color.parseColor(color)) < 0.5;
}

}
11 changes: 11 additions & 0 deletions cordova-plugin-moodleapp/src/ts/plugins/SystemUI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,15 @@ export class SystemUI {
});
}

/**
* Set status bar color.
*
* @param color Color.
*/
async setStatusBarColor(color: string): Promise<void> {
await new Promise((resolve, reject) => {
cordova.exec(resolve, reject, 'SystemUI', 'setStatusBarColor', [color]);
});
}

}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@
"cordova-plugin-geolocation": "^4.1.0",
"cordova-plugin-ionic-keyboard": "^2.2.0",
"cordova-plugin-media-capture": "3.0.3",
"cordova-plugin-moodleapp": "file:cordova-plugin-moodleapp",
"cordova-plugin-network-information": "^3.0.0",
"cordova-plugin-prevent-override": "^1.0.1",
"cordova-plugin-screen-orientation": "^3.0.2",
Expand Down Expand Up @@ -161,6 +160,7 @@
"check-es-compat": "^1.1.1",
"compare-versions": "^4.1.4",
"concurrently": "^8.2.0",
"cordova-plugin-moodleapp": "file:cordova-plugin-moodleapp",
"cross-env": "^7.0.3",
"eslint": "^7.25.0",
"eslint-config-prettier": "^8.3.0",
Expand Down
3 changes: 1 addition & 2 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,7 @@ export class AppComponent implements OnInit, AfterViewInit {
const isOnline = CoreNetwork.isOnline();
CoreDomUtils.toggleModeClass('core-offline', !isOnline, { includeLegacy: true });

// Set StatusBar properties.
CoreApp.setStatusBarColor();
CoreApp.setSystemUIColors();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/core/features/settings/services/settings-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ export class CoreSettingsHelperProvider {
CoreDomUtils.toggleModeClass('dark', enable, { includeLegacy: true });
this.darkModeObservable.next(enable);

CoreApp.setStatusBarColor();
CoreApp.setSystemUIColors();
}
}

Expand Down
12 changes: 5 additions & 7 deletions src/core/features/styles/services/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,7 @@ export class CoreStylesService {
this.disableStyleElement(style, true);
});

// Set StatusBar properties.
CoreApp.setStatusBarColor();
CoreApp.setSystemUIColors();
}

/**
Expand Down Expand Up @@ -341,7 +340,7 @@ export class CoreStylesService {
this.disableStyleElementByName(siteId, sourceName, false);
}

CoreApp.setStatusBarColor();
CoreApp.setSystemUIColors();
}
}

Expand Down Expand Up @@ -371,8 +370,7 @@ export class CoreStylesService {
}));

if (!disabled) {
// Set StatusBar properties.
CoreApp.setStatusBarColor();
CoreApp.setSystemUIColors();
}
}

Expand All @@ -390,7 +388,7 @@ export class CoreStylesService {
await this.setStyle(CoreStylesService.TMP_SITE_ID, handler, false, config);
}));

CoreApp.setStatusBarColor();
CoreApp.setSystemUIColors();
}

/**
Expand Down Expand Up @@ -438,7 +436,7 @@ export class CoreStylesService {
}
delete this.stylesEls[siteId];

CoreApp.setStatusBarColor();
CoreApp.setSystemUIColors();
}
}

Expand Down
49 changes: 40 additions & 9 deletions src/core/services/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
import { Injectable } from '@angular/core';

import { CoreDB } from '@services/db';
import { CoreEvents } from '@singletons/events';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';

import { makeSingleton, Keyboard, StatusBar } from '@singletons';
import { makeSingleton, Keyboard } from '@singletons';
import { CoreLogger } from '@singletons/logger';
import { CoreColors } from '@singletons/colors';
import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app';
Expand All @@ -31,6 +31,8 @@ import { CorePromisedValue } from '@classes/promised-value';
import { Subscription } from 'rxjs';
import { CorePlatform } from '@services/platform';
import { CoreNetwork, CoreNetworkConnection } from '@services/network';
import { CoreNative } from '@features/native/services/native';
import { CoreMainMenuProvider } from '@features/mainmenu/services/mainmenu';

/**
* Factory to provide some global functionalities, like access to the global app database.
Expand All @@ -56,9 +58,14 @@ export class CoreAppProvider {
protected keyboardClosing = false;
protected redirect?: CoreRedirectData;
protected schemaVersionsTable = asyncInstance<CoreDatabaseTable<SchemaVersionsDBEntry, 'name'>>();
protected mainMenuListener?: CoreEventObserver;

constructor() {
this.logger = CoreLogger.getInstance('CoreAppProvider');
if (CorePlatform.isAndroid()) {
this.mainMenuListener =
CoreEvents.on(CoreMainMenuProvider.MAIN_MENU_VISIBILITY_UPDATED, () => this.setAndroidNavigationBarColor());
}
}

/**
Expand Down Expand Up @@ -200,7 +207,7 @@ export class CoreAppProvider {
* @returns Store URL.
*/
getAppStoreUrl(storesConfig: CoreStoreConfig): string | undefined {
if (this.isIOS() && storesConfig.ios) {
if (CorePlatform.isIOS() && storesConfig.ios) {
return 'itms-apps://itunes.apple.com/app/' + storesConfig.ios;
}

Expand Down Expand Up @@ -618,6 +625,14 @@ export class CoreAppProvider {
return () => false;
}

/**
* Set System UI Colors.
*/
setSystemUIColors(): void {
this.setStatusBarColor();
this.setAndroidNavigationBarColor();
}

/**
* Set StatusBar color depending on platform.
*
Expand All @@ -638,13 +653,11 @@ export class CoreAppProvider {
const useLightText = CoreColors.isWhiteContrastingBetter(color);

// styleDefault will use white text on iOS when darkmode is on. Force the background to black.
if (this.isIOS() && !useLightText && window.matchMedia('(prefers-color-scheme: dark)').matches) {
StatusBar.backgroundColorByHexString('#000000');
StatusBar.styleLightContent();
} else {
StatusBar.backgroundColorByHexString(color);
useLightText ? StatusBar.styleLightContent() : StatusBar.styleDefault();
if (CorePlatform.isIOS() && !useLightText && window.matchMedia('(prefers-color-scheme: dark)').matches) {
color = '#000000';
}

CoreNative.plugin('systemUI')?.setStatusBarColor(color);
}

/**
Expand Down Expand Up @@ -684,6 +697,24 @@ export class CoreAppProvider {
}
}

/**
* Set NavigationBar color for Android
*
* @param color RGB color to use as background. If not set the css variable will be read.
*/
protected setAndroidNavigationBarColor(color?: string): void {
if (!CorePlatform.isAndroid()) {
return;
}

if (!color) {
// Get the default color to change it.
color = CoreColors.getBottomPageBackgroundColor();
}

CoreNative.plugin('systemUI')?.setNavigationBarColor(color);
}

}

export const CoreApp = makeSingleton(CoreAppProvider);
Expand Down
Loading

0 comments on commit ef8a8c5

Please sign in to comment.