forgot to push

This commit is contained in:
olcxja 2026-05-08 18:07:14 +02:00
commit 4942043ae3
28 changed files with 2458 additions and 0 deletions

View file

@ -0,0 +1,81 @@
ext {
capacitorVersion = System.getenv('CAPACITOR_VERSION')
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
androidxCoreVersion = project.hasProperty('androidxCoreVersion') ? rootProject.ext.androidxCoreVersion : '1.17.0'
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
}
buildscript {
repositories {
google()
mavenCentral()
maven {
url = "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:8.13.0'
if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") {
classpath 'io.github.gradle-nexus:publish-plugin:1.3.0'
}
}
}
apply plugin: 'com.android.library'
if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") {
apply plugin: 'io.github.gradle-nexus.publish-plugin'
apply from: file('../../scripts/android/publish-root.gradle')
apply from: file('../../scripts/android/publish-module.gradle')
}
android {
namespace = "com.capacitorjs.plugins.statusbar"
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
defaultConfig {
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError = false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
publishing {
singleVariant("release")
}
}
repositories {
google()
mavenCentral()
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") {
implementation "com.capacitorjs:core:$capacitorVersion"
} else {
implementation project(':capacitor-android')
}
implementation "androidx.core:core:$androidxCoreVersion"
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
}

View file

@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View file

@ -0,0 +1,234 @@
package com.capacitorjs.plugins.statusbar;
import android.content.res.Configuration;
import android.graphics.Color;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.ColorUtils;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
public class StatusBar {
public static final String statusBarVisibilityChanged = "statusBarVisibilityChanged";
public static final String statusBarOverlayChanged = "statusBarOverlayChanged";
private int currentStatusBarColor;
private final ChangeListener listener;
private final AppCompatActivity activity;
private String currentStyle = "DEFAULT";
public StatusBar(AppCompatActivity activity, StatusBarConfig config, ChangeListener listener) {
// save initial color of the status bar
this.activity = activity;
this.currentStatusBarColor = getStatusBarColorDeprecated();
this.listener = listener;
setBackgroundColor(config.getBackgroundColor());
setStyle(config.getStyle());
setOverlaysWebView(config.isOverlaysWebView());
StatusBarInfo info = getInfo();
info.setVisible(true);
listener.onChange(statusBarOverlayChanged, info);
}
public void setStyle(String style) {
Window window = activity.getWindow();
View decorView = window.getDecorView();
this.currentStyle = style;
if (style.equals("DEFAULT")) {
style = getStyleForTheme();
}
WindowInsetsControllerCompat windowInsetsControllerCompat = WindowCompat.getInsetsController(window, decorView);
windowInsetsControllerCompat.setAppearanceLightStatusBars(!style.equals("DARK"));
}
private String getStyleForTheme() {
int currentNightMode = activity.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (currentNightMode != Configuration.UI_MODE_NIGHT_YES) {
return "LIGHT";
}
return "DARK";
}
public void updateStyle() {
setStyle(this.currentStyle);
}
public void setBackgroundColor(int color) {
Window window = activity.getWindow();
if (shouldSetStatusBarColor(isEdgeToEdgeOptOutEnabled(window))) {
clearTranslucentStatusFlagDeprecated();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
setStatusBarColorDeprecated(color);
currentStatusBarColor = color;
// only set foreground color if style is default
if (currentStyle.equals("DEFAULT")) {
// determine if the color is light or dark using luminance and set icon color
boolean isLightColor = ColorUtils.calculateLuminance(color) > 0.5;
WindowInsetsControllerCompat insetsController = WindowCompat.getInsetsController(window, window.getDecorView());
insetsController.setAppearanceLightStatusBars(isLightColor);
}
}
}
public void hide() {
View decorView = activity.getWindow().getDecorView();
WindowInsetsControllerCompat windowInsetsControllerCompat = WindowCompat.getInsetsController(activity.getWindow(), decorView);
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.statusBars());
StatusBarInfo info = getInfo();
info.setVisible(false);
listener.onChange(statusBarVisibilityChanged, info);
}
public void show() {
View decorView = activity.getWindow().getDecorView();
WindowInsetsControllerCompat windowInsetsControllerCompat = WindowCompat.getInsetsController(activity.getWindow(), decorView);
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.statusBars());
StatusBarInfo info = getInfo();
info.setVisible(true);
listener.onChange(statusBarVisibilityChanged, info);
}
public void setOverlaysWebView(Boolean overlays) {
View decorView = activity.getWindow().getDecorView();
int uiOptions = getSystemUiVisibilityDeprecated(decorView);
if (overlays) {
// Sets the layout to a fullscreen one that does not hide the actual status bar, so the WebView is displayed behind it.
uiOptions = uiOptions | getSystemUiFlagLayoutStableDeprecated() | getSystemUiFlagLayoutFullscreenDeprecated();
setSystemUiVisibilityDeprecated(decorView, uiOptions);
currentStatusBarColor = getStatusBarColorDeprecated();
setStatusBarColorDeprecated(Color.TRANSPARENT);
} else {
// Sets the layout to a normal one that displays the WebView below the status bar.
uiOptions = uiOptions & ~getSystemUiFlagLayoutStableDeprecated() & ~getSystemUiFlagLayoutFullscreenDeprecated();
setSystemUiVisibilityDeprecated(decorView, uiOptions);
// recover the previous color of the status bar
setStatusBarColorDeprecated(currentStatusBarColor);
}
listener.onChange(statusBarOverlayChanged, getInfo());
}
private boolean shouldSetStatusBarColor(boolean hasOptOut) {
boolean canSetStatusBar;
int deviceApi = Build.VERSION.SDK_INT;
if (deviceApi < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
// device below Android 15 - can always set status bar
canSetStatusBar = true;
} else if (deviceApi == Build.VERSION_CODES.VANILLA_ICE_CREAM) {
canSetStatusBar = hasOptOut; // app targets 15 - can set status bar if opted out
} else {
canSetStatusBar = false; // app targets 16 - opt-out ignored or app targets 15 but there is not opt out
}
return canSetStatusBar;
}
private boolean isEdgeToEdgeOptOutEnabled(Window window) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
TypedValue value = new TypedValue();
window.getContext().getTheme().resolveAttribute(android.R.attr.windowOptOutEdgeToEdgeEnforcement, value, true);
return value.data != 0; // value is set to -1 on true as of Android 15, so we have to do this.
}
return false;
}
private boolean getIsOverlaid() {
return (
(getSystemUiVisibilityDeprecated(activity.getWindow().getDecorView()) & getSystemUiFlagLayoutFullscreenDeprecated()) ==
getSystemUiFlagLayoutFullscreenDeprecated()
);
}
public StatusBarInfo getInfo() {
Window window = activity.getWindow();
WindowInsetsCompat windowInsetsCompat = ViewCompat.getRootWindowInsets(window.getDecorView());
boolean isVisible = windowInsetsCompat != null && windowInsetsCompat.isVisible(WindowInsetsCompat.Type.statusBars());
StatusBarInfo info = new StatusBarInfo();
info.setStyle(getStyle());
info.setOverlays(getIsOverlaid());
info.setVisible(isVisible);
info.setColor(String.format("#%06X", (0xFFFFFF & getStatusBarColorDeprecated())));
info.setHeight(getStatusBarHeight());
return info;
}
private String getStyle() {
View decorView = activity.getWindow().getDecorView();
String style = "DARK";
WindowInsetsControllerCompat windowInsetsControllerCompat = WindowCompat.getInsetsController(activity.getWindow(), decorView);
if (windowInsetsControllerCompat.isAppearanceLightStatusBars()) {
style = "LIGHT";
}
return style;
}
private int getStatusBarHeight() {
DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowInsets insets = activity.getWindowManager().getCurrentWindowMetrics().getWindowInsets();
return (int) (insets.getInsets(WindowInsets.Type.statusBars()).top / metrics.density);
}
WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets();
if (insets != null) {
return getSystemWindowInsetTopDeprecated(insets, metrics);
}
// Fallback if the insets are not available
return 0;
}
public interface ChangeListener {
void onChange(String eventName, StatusBarInfo info);
}
@SuppressWarnings("deprecation")
private int getStatusBarColorDeprecated() {
return activity.getWindow().getStatusBarColor();
}
@SuppressWarnings("deprecation")
private void setStatusBarColorDeprecated(int color) {
activity.getWindow().setStatusBarColor(color);
}
@SuppressWarnings("deprecation")
private void clearTranslucentStatusFlagDeprecated() {
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
@SuppressWarnings("deprecation")
private int getSystemUiVisibilityDeprecated(View decorView) {
return decorView.getSystemUiVisibility();
}
@SuppressWarnings("deprecation")
private void setSystemUiVisibilityDeprecated(View decorView, int uiOptions) {
decorView.setSystemUiVisibility(uiOptions);
}
@SuppressWarnings("deprecation")
private int getSystemUiFlagLayoutStableDeprecated() {
return View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
}
@SuppressWarnings("deprecation")
private int getSystemUiFlagLayoutFullscreenDeprecated() {
return View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
}
@SuppressWarnings("deprecation")
private int getSystemWindowInsetTopDeprecated(WindowInsets insets, DisplayMetrics metrics) {
return (int) (insets.getSystemWindowInsetTop() / metrics.density);
}
}

View file

@ -0,0 +1,34 @@
package com.capacitorjs.plugins.statusbar;
import com.getcapacitor.util.WebColor;
public class StatusBarConfig {
private boolean overlaysWebView = true;
private Integer backgroundColor = WebColor.parseColor("#000000");
private String style = "DEFAULT";
public boolean isOverlaysWebView() {
return overlaysWebView;
}
public void setOverlaysWebView(boolean overlaysWebView) {
this.overlaysWebView = overlaysWebView;
}
public Integer getBackgroundColor() {
return backgroundColor;
}
public void setBackgroundColor(Integer backgroundColor) {
this.backgroundColor = backgroundColor;
}
public String getStyle() {
return style;
}
public void setStyle(String style) {
this.style = style;
}
}

View file

@ -0,0 +1,52 @@
package com.capacitorjs.plugins.statusbar;
import java.io.Serializable;
public class StatusBarInfo implements Serializable {
private boolean overlays;
private boolean visible;
private String style;
private String color;
private int height;
public boolean isOverlays() {
return overlays;
}
public void setOverlays(boolean overlays) {
this.overlays = overlays;
}
public boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
public String getStyle() {
return style;
}
public void setStyle(String style) {
this.style = style;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}

View file

@ -0,0 +1,134 @@
package com.capacitorjs.plugins.statusbar;
import android.content.res.Configuration;
import com.getcapacitor.JSObject;
import com.getcapacitor.Logger;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.getcapacitor.util.WebColor;
import java.util.Locale;
@CapacitorPlugin(name = "StatusBar")
public class StatusBarPlugin extends Plugin {
private StatusBar implementation;
@Override
public void load() {
StatusBarConfig config = getStatusBarConfig();
implementation = new StatusBar(getActivity(), config, (eventName, info) -> notifyListeners(eventName, toJSObject(info), true));
}
private StatusBarConfig getStatusBarConfig() {
StatusBarConfig config = new StatusBarConfig();
String backgroundColor = getConfig().getString("backgroundColor");
if (backgroundColor != null) {
try {
config.setBackgroundColor(WebColor.parseColor(backgroundColor));
} catch (IllegalArgumentException ex) {
Logger.debug("Background color not applied");
}
}
config.setStyle(styleFromConfig(getConfig().getString("style", config.getStyle())));
config.setOverlaysWebView(getConfig().getBoolean("overlaysWebView", config.isOverlaysWebView()));
return config;
}
private String styleFromConfig(String style) {
switch (style.toLowerCase()) {
case "lightcontent":
case "dark":
return "DARK";
case "darkcontent":
case "light":
return "LIGHT";
case "default":
default:
return "DEFAULT";
}
}
@Override
protected void handleOnConfigurationChanged(Configuration newConfig) {
super.handleOnConfigurationChanged(newConfig);
implementation.updateStyle();
}
@PluginMethod
public void setStyle(final PluginCall call) {
final String style = call.getString("style");
if (style == null) {
call.reject("Style must be provided");
return;
}
getBridge().executeOnMainThread(() -> {
implementation.setStyle(style);
call.resolve();
});
}
@PluginMethod
public void setBackgroundColor(final PluginCall call) {
final String color = call.getString("color");
if (color == null) {
call.reject("Color must be provided");
return;
}
getBridge().executeOnMainThread(() -> {
try {
final int parsedColor = WebColor.parseColor(color.toUpperCase(Locale.ROOT));
implementation.setBackgroundColor(parsedColor);
call.resolve();
} catch (IllegalArgumentException ex) {
call.reject("Invalid color provided. Must be a hex string (ex: #ff0000");
}
});
}
@PluginMethod
public void hide(final PluginCall call) {
// Hide the status bar.
getBridge().executeOnMainThread(() -> {
implementation.hide();
call.resolve();
});
}
@PluginMethod
public void show(final PluginCall call) {
// Show the status bar.
getBridge().executeOnMainThread(() -> {
implementation.show();
call.resolve();
});
}
@PluginMethod
public void getInfo(final PluginCall call) {
StatusBarInfo info = implementation.getInfo();
call.resolve(toJSObject(info));
}
@PluginMethod
public void setOverlaysWebView(final PluginCall call) {
final Boolean overlay = call.getBoolean("overlay", true);
getBridge().executeOnMainThread(() -> {
implementation.setOverlaysWebView(overlay);
call.resolve();
});
}
private JSObject toJSObject(StatusBarInfo info) {
JSObject data = new JSObject();
data.put("visible", info.isVisible());
data.put("style", info.getStyle());
data.put("color", info.getColor());
data.put("overlays", info.isOverlays());
data.put("height", info.getHeight());
return data;
}
}