Skip to content

Commit 9c44041

Browse files
authored
Merge pull request #57 from jingle-jew/dev
Merge dev into master
2 parents 65cb4a0 + 2af673a commit 9c44041

13 files changed

Lines changed: 268 additions & 70 deletions

File tree

android-app/android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
android:label="@string/app_name"
88
android:roundIcon="@mipmap/ic_launcher_round"
99
android:supportsRtl="true"
10-
android:theme="@style/AppTheme">
10+
android:theme="@style/AppTheme.NoActionBar">
1111

1212
<activity
1313
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"

android-app/android/app/src/main/java/app/mha/controlhub/MainActivity.java

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package app.mha.controlhub;
22

3-
import android.os.Bundle;
3+
import android.content.Intent;
44
import android.content.res.Configuration;
55
import android.graphics.Color;
6+
import android.os.Bundle;
7+
import android.view.View;
68
import android.view.Window;
7-
import android.webkit.WebView;
89
import android.view.WindowManager;
10+
import android.webkit.WebView;
911

1012
import androidx.core.graphics.Insets;
1113
import androidx.core.view.ViewCompat;
@@ -20,29 +22,51 @@ public class MainActivity extends BridgeActivity {
2022

2123
@Override
2224
protected void onCreate(Bundle savedInstanceState) {
25+
applyEdgeToEdgeWindowFlags();
2326
super.onCreate(savedInstanceState);
24-
configureEdgeToEdge();
27+
hideNativeChrome();
28+
applyEdgeToEdgeWindowFlags();
2529
installInsetsListener();
26-
republishSafeAreaInsets();
30+
schedulePostResumeEdgeToEdgeSync();
31+
republishSafeAreaInsets(true);
2732
}
2833

2934
@Override
3035
public void onWindowFocusChanged(boolean hasFocus) {
3136
super.onWindowFocusChanged(hasFocus);
3237
if (hasFocus) {
33-
syncSystemBarAppearance();
34-
republishSafeAreaInsets();
38+
hideNativeChrome();
39+
applyEdgeToEdgeWindowFlags();
40+
schedulePostResumeEdgeToEdgeSync();
41+
republishSafeAreaInsets(true);
3542
}
3643
}
3744

3845
@Override
39-
protected void onResume() {
46+
public void onResume() {
4047
super.onResume();
41-
syncSystemBarAppearance();
42-
republishSafeAreaInsets();
48+
hideNativeChrome();
49+
applyEdgeToEdgeWindowFlags();
50+
schedulePostResumeEdgeToEdgeSync();
51+
republishSafeAreaInsets(true);
4352
}
4453

45-
private void configureEdgeToEdge() {
54+
@Override
55+
protected void onNewIntent(Intent intent) {
56+
super.onNewIntent(intent);
57+
hideNativeChrome();
58+
applyEdgeToEdgeWindowFlags();
59+
schedulePostResumeEdgeToEdgeSync();
60+
republishSafeAreaInsets(true);
61+
}
62+
63+
private void hideNativeChrome() {
64+
if (getSupportActionBar() != null) {
65+
getSupportActionBar().hide();
66+
}
67+
}
68+
69+
private void applyEdgeToEdgeWindowFlags() {
4670
Window window = getWindow();
4771
WindowCompat.setDecorFitsSystemWindows(window, false);
4872
window.setStatusBarColor(Color.TRANSPARENT);
@@ -65,14 +89,23 @@ private void configureEdgeToEdge() {
6589
syncSystemBarAppearance();
6690
}
6791

92+
private void schedulePostResumeEdgeToEdgeSync() {
93+
View decorView = getWindow().getDecorView();
94+
decorView.post(() -> {
95+
applyEdgeToEdgeWindowFlags();
96+
ViewCompat.requestApplyInsets(decorView);
97+
republishSafeAreaInsets(true);
98+
});
99+
}
100+
68101
private void installInsetsListener() {
69102
ViewCompat.setOnApplyWindowInsetsListener(
70103
getWindow().getDecorView(),
71104
(view, windowInsets) -> {
72105
Insets safeInsets = windowInsets.getInsets(
73106
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()
74107
);
75-
publishSafeAreaInsets(safeInsets);
108+
publishSafeAreaInsets(safeInsets, false);
76109
return windowInsets;
77110
}
78111
);
@@ -94,18 +127,18 @@ private void syncSystemBarAppearance() {
94127
controller.setAppearanceLightNavigationBars(useDarkSystemBarIcons);
95128
}
96129

97-
private void republishSafeAreaInsets() {
130+
private void republishSafeAreaInsets(boolean force) {
98131
WindowInsetsCompat rootInsets = ViewCompat.getRootWindowInsets(getWindow().getDecorView());
99132
if (rootInsets == null) return;
100133
Insets safeInsets = rootInsets.getInsets(
101134
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()
102135
);
103-
publishSafeAreaInsets(safeInsets);
136+
publishSafeAreaInsets(safeInsets, force);
104137
}
105138

106-
private void publishSafeAreaInsets(Insets insets) {
139+
private void publishSafeAreaInsets(Insets insets, boolean force) {
107140
if (insets == null) return;
108-
if (insets.equals(lastPublishedInsets)) return;
141+
if (!force && insets.equals(lastPublishedInsets)) return;
109142
lastPublishedInsets = insets;
110143

111144
WebView webView = getBridge() != null ? getBridge().getWebView() : null;

android-app/android/app/src/main/res/values/styles.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
<item name="windowNoTitle">true</item>
1515
<item name="android:windowActionBar">false</item>
1616
<item name="android:windowNoTitle">true</item>
17+
<item name="android:windowFullscreen">true</item>
18+
<item name="android:windowIsTranslucent">false</item>
1719
<item name="android:background">@android:color/transparent</item>
1820
<item name="android:windowBackground">@android:color/transparent</item>
1921
<item name="android:statusBarColor">@android:color/transparent</item>
@@ -26,6 +28,12 @@
2628

2729
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
2830
<item name="postSplashScreenTheme">@style/AppTheme.NoActionBar</item>
31+
<item name="windowActionBar">false</item>
32+
<item name="windowNoTitle">true</item>
33+
<item name="android:windowActionBar">false</item>
34+
<item name="android:windowNoTitle">true</item>
35+
<item name="android:windowFullscreen">true</item>
36+
<item name="android:windowIsTranslucent">false</item>
2937
<item name="android:background">@drawable/splash</item>
3038
<item name="android:windowBackground">@drawable/splash</item>
3139
<item name="android:statusBarColor">@android:color/transparent</item>
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

android-app/dist/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
66
<meta name="theme-color" content="#030406" />
77
<title>MHA Widget Hub</title>
8-
<script type="module" crossorigin src="/assets/index-P_99XXX_.js"></script>
8+
<script type="module" crossorigin src="/assets/index-BAj44Ij5.js"></script>
99
<link rel="stylesheet" crossorigin href="/assets/index-BblYIv4W.css">
1010
</head>
1111
<body>

android-app/scripts/apply-android-immersive.mjs

Lines changed: 142 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,66 +14,179 @@ const mainActivityPath = join(
1414
"MainActivity.java",
1515
);
1616

17-
const stableImmersiveMainActivity = `package app.mha.controlhub;
17+
const edgeToEdgeMainActivity = `package app.mha.controlhub;
1818
19-
import android.os.Build;
19+
import android.content.Intent;
20+
import android.content.res.Configuration;
21+
import android.graphics.Color;
2022
import android.os.Bundle;
2123
import android.view.View;
2224
import android.view.Window;
23-
import android.view.WindowInsets;
24-
import android.view.WindowInsetsController;
25+
import android.view.WindowManager;
26+
import android.webkit.WebView;
27+
28+
import androidx.core.graphics.Insets;
29+
import androidx.core.view.ViewCompat;
30+
import androidx.core.view.WindowCompat;
31+
import androidx.core.view.WindowInsetsCompat;
32+
import androidx.core.view.WindowInsetsControllerCompat;
2533
2634
import com.getcapacitor.BridgeActivity;
2735
2836
public class MainActivity extends BridgeActivity {
37+
private Insets lastPublishedInsets = Insets.NONE;
38+
2939
@Override
3040
protected void onCreate(Bundle savedInstanceState) {
41+
applyEdgeToEdgeWindowFlags();
3142
super.onCreate(savedInstanceState);
32-
enableStableImmersiveMode();
43+
hideNativeChrome();
44+
applyEdgeToEdgeWindowFlags();
45+
installInsetsListener();
46+
schedulePostResumeEdgeToEdgeSync();
47+
republishSafeAreaInsets(true);
3348
}
3449
3550
@Override
3651
public void onWindowFocusChanged(boolean hasFocus) {
3752
super.onWindowFocusChanged(hasFocus);
3853
if (hasFocus) {
39-
enableStableImmersiveMode();
54+
hideNativeChrome();
55+
applyEdgeToEdgeWindowFlags();
56+
schedulePostResumeEdgeToEdgeSync();
57+
republishSafeAreaInsets(true);
58+
}
59+
}
60+
61+
@Override
62+
public void onResume() {
63+
super.onResume();
64+
hideNativeChrome();
65+
applyEdgeToEdgeWindowFlags();
66+
schedulePostResumeEdgeToEdgeSync();
67+
republishSafeAreaInsets(true);
68+
}
69+
70+
@Override
71+
protected void onNewIntent(Intent intent) {
72+
super.onNewIntent(intent);
73+
hideNativeChrome();
74+
applyEdgeToEdgeWindowFlags();
75+
schedulePostResumeEdgeToEdgeSync();
76+
republishSafeAreaInsets(true);
77+
}
78+
79+
private void hideNativeChrome() {
80+
if (getSupportActionBar() != null) {
81+
getSupportActionBar().hide();
4082
}
4183
}
4284
43-
private void enableStableImmersiveMode() {
85+
private void applyEdgeToEdgeWindowFlags() {
4486
Window window = getWindow();
87+
WindowCompat.setDecorFitsSystemWindows(window, false);
88+
window.setStatusBarColor(Color.TRANSPARENT);
89+
window.setNavigationBarColor(Color.TRANSPARENT);
90+
91+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
92+
WindowManager.LayoutParams attributes = window.getAttributes();
93+
attributes.layoutInDisplayCutoutMode =
94+
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
95+
window.setAttributes(attributes);
96+
}
97+
98+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
99+
window.setStatusBarContrastEnforced(false);
100+
window.setNavigationBarContrastEnforced(false);
101+
}
102+
103+
WebView webView = getBridge() != null ? getBridge().getWebView() : null;
104+
if (webView != null) webView.setBackgroundColor(Color.TRANSPARENT);
105+
syncSystemBarAppearance();
106+
}
107+
108+
private void schedulePostResumeEdgeToEdgeSync() {
109+
View decorView = getWindow().getDecorView();
110+
decorView.post(() -> {
111+
applyEdgeToEdgeWindowFlags();
112+
ViewCompat.requestApplyInsets(decorView);
113+
republishSafeAreaInsets(true);
114+
});
115+
}
45116
46-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
47-
// Keep Android's navigation / gesture area stable. Hiding the navigation
48-
// bars in WebView immersive mode can make Android recalculate the bottom
49-
// viewport while the user scrolls near the end of the page, which can
50-
// produce visible flashes on glass-heavy layouts.
51-
window.setDecorFitsSystemWindows(true);
52-
WindowInsetsController controller = window.getInsetsController();
53-
54-
if (controller != null) {
55-
controller.hide(WindowInsets.Type.statusBars());
56-
controller.setSystemBarsBehavior(
57-
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
117+
private void installInsetsListener() {
118+
ViewCompat.setOnApplyWindowInsetsListener(
119+
getWindow().getDecorView(),
120+
(view, windowInsets) -> {
121+
Insets safeInsets = windowInsets.getInsets(
122+
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()
58123
);
124+
publishSafeAreaInsets(safeInsets, false);
125+
return windowInsets;
59126
}
60-
return;
61-
}
127+
);
128+
ViewCompat.requestApplyInsets(getWindow().getDecorView());
129+
}
130+
131+
private void syncSystemBarAppearance() {
132+
Window window = getWindow();
133+
WindowInsetsControllerCompat controller = WindowCompat.getInsetsController(
134+
window,
135+
window.getDecorView()
136+
);
137+
if (controller == null) return;
138+
139+
boolean useDarkSystemBarIcons =
140+
(getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
141+
!= Configuration.UI_MODE_NIGHT_YES;
142+
controller.setAppearanceLightStatusBars(useDarkSystemBarIcons);
143+
controller.setAppearanceLightNavigationBars(useDarkSystemBarIcons);
144+
}
62145
63-
// Legacy Android fallback: hide only the status bar. Do not request
64-
// HIDE_NAVIGATION / LAYOUT_HIDE_NAVIGATION here, because that is the part
65-
// most likely to destabilize the bottom gesture/navigation area.
66-
window.getDecorView().setSystemUiVisibility(
67-
View.SYSTEM_UI_FLAG_FULLSCREEN
68-
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
146+
private void republishSafeAreaInsets(boolean force) {
147+
WindowInsetsCompat rootInsets = ViewCompat.getRootWindowInsets(getWindow().getDecorView());
148+
if (rootInsets == null) return;
149+
Insets safeInsets = rootInsets.getInsets(
150+
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()
69151
);
152+
publishSafeAreaInsets(safeInsets, force);
153+
}
154+
155+
private void publishSafeAreaInsets(Insets insets, boolean force) {
156+
if (insets == null) return;
157+
if (!force && insets.equals(lastPublishedInsets)) return;
158+
lastPublishedInsets = insets;
159+
160+
WebView webView = getBridge() != null ? getBridge().getWebView() : null;
161+
if (webView == null) return;
162+
163+
final int top = insets.top;
164+
final int right = insets.right;
165+
final int bottom = insets.bottom;
166+
final int left = insets.left;
167+
final String script =
168+
"(function(){" +
169+
"const insets={top:" + top + ",right:" + right + ",bottom:" + bottom + ",left:" + left + "};" +
170+
"window.__MHA_ANDROID_EDGE_TO_EDGE__=true;" +
171+
"window.__MHA_ANDROID_INSETS__=insets;" +
172+
"const root=document.documentElement;" +
173+
"root.dataset.mhaAndroidEdgeToEdge='true';" +
174+
"root.style.setProperty('--mha-safe-top',insets.top+'px');" +
175+
"root.style.setProperty('--mha-safe-right',insets.right+'px');" +
176+
"root.style.setProperty('--mha-safe-bottom',insets.bottom+'px');" +
177+
"root.style.setProperty('--mha-safe-left',insets.left+'px');" +
178+
"document.querySelectorAll('mha-widget-hub').forEach((el)=>el.classList.add('mha-android-edge-to-edge'));" +
179+
"window.dispatchEvent(new CustomEvent('mha:android-insets-changed',{detail:insets}));" +
180+
"})();";
181+
182+
webView.post(() -> webView.evaluateJavascript(script, null));
70183
}
71184
}
72185
`;
73186

74187
mkdirSync(dirname(mainActivityPath), { recursive: true });
75-
writeFileSync(mainActivityPath, stableImmersiveMainActivity, "utf8");
76-
console.log("Applied Android stable immersive mode to MainActivity.java");
188+
writeFileSync(mainActivityPath, edgeToEdgeMainActivity, "utf8");
189+
console.log("Applied Android edge-to-edge MainActivity.java template");
77190

78191
if (!existsSync(mainActivityPath)) {
79192
process.exitCode = 1;

0 commit comments

Comments
 (0)