fix(android): use native safe area insets for proper status bar padding

Install capacitor-plugin-safe-area to read native safe area insets and
inject them as CSS variables, working around Capacitor 7's broken
adjustMarginsForEdgeToEdge and Android WebView's unreliable
env(safe-area-inset-*) values.

- Add _initSafeAreaInsets() to GlobalThemeService
- Remove broken adjustMarginsForEdgeToEdge: 'auto' from capacitor config
- Clamp context menu trigger Y to safe area top
- Fix light mode status bar color to match theme (#f8f8f7)
This commit is contained in:
Johannes Millan 2026-02-11 17:59:59 +01:00
parent a174647e00
commit 4c4a0a3afd
9 changed files with 61 additions and 462 deletions

View file

@ -11,6 +11,7 @@ apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-keyboard')
implementation project(':capacitor-status-bar')
implementation project(':capacitor-plugin-safe-area')
implementation project(':capacitor-app')
implementation project(':capacitor-filesystem')
implementation project(':capacitor-local-notifications')

View file

@ -8,6 +8,9 @@ project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor
include ':capacitor-status-bar'
project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
include ':capacitor-plugin-safe-area'
project(':capacitor-plugin-safe-area').projectDir = new File('../node_modules/capacitor-plugin-safe-area/android')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')

View file

@ -18,9 +18,7 @@ const config: CapacitorConfig = {
resizeOnFullScreen: true,
},
},
android: {
adjustMarginsForEdgeToEdge: 'auto',
},
android: {},
ios: {
// Content inset for safe areas (notch, home indicator)
contentInset: 'never',

View file

@ -13,6 +13,7 @@ def capacitor_pods
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
pod 'CapacitorPluginSafeArea', :path => '../../node_modules/capacitor-plugin-safe-area'
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem'
pod 'CapacitorLocalNotifications', :path => '../../node_modules/@capacitor/local-notifications'

464
package-lock.json generated
View file

@ -17,6 +17,7 @@
"@capacitor/status-bar": "^7.0.4",
"@material-symbols/font-400": "^0.40.2",
"@noble/ciphers": "^2.1.1",
"capacitor-plugin-safe-area": "^4.0.3",
"electron-dl": "^3.5.2",
"electron-localshortcut": "^3.2.1",
"electron-log": "^5.4.3",
@ -392,40 +393,6 @@
}
}
},
"node_modules/@angular-devkit/architect/node_modules/chokidar": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"readdirp": "^5.0.0"
},
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular-devkit/architect/node_modules/readdirp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular-devkit/build-angular": {
"version": "21.0.5",
"dev": true,
@ -1220,114 +1187,6 @@
"win32"
]
},
"node_modules/@angular-devkit/build-angular/node_modules/@vitest/expect": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
"integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@types/chai": "^5.2.2",
"@vitest/spy": "4.0.18",
"@vitest/utils": "4.0.18",
"chai": "^6.2.1",
"tinyrainbow": "^3.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@vitest/pretty-format": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz",
"integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"tinyrainbow": "^3.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@vitest/runner": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz",
"integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@vitest/utils": "4.0.18",
"pathe": "^2.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@vitest/snapshot": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz",
"integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@vitest/pretty-format": "4.0.18",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@vitest/snapshot/node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@vitest/spy": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz",
"integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/@vitest/utils": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz",
"integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@vitest/pretty-format": "4.0.18",
"tinyrainbow": "^3.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/ansi-regex": {
"version": "6.2.2",
"dev": true,
@ -1339,18 +1198,6 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/chai": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/chalk": {
"version": "5.6.2",
"dev": true,
@ -1589,18 +1436,6 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/tinyexec": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
"integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/tinyglobby": {
"version": "0.2.15",
"dev": true,
@ -1616,18 +1451,6 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/tinyrainbow": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
"integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/vite": {
"version": "7.2.2",
"dev": true,
@ -2181,98 +2004,6 @@
"@esbuild/win32-x64": "0.25.12"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/vitest": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@vitest/expect": "4.0.18",
"@vitest/mocker": "4.0.18",
"@vitest/pretty-format": "4.0.18",
"@vitest/runner": "4.0.18",
"@vitest/snapshot": "4.0.18",
"@vitest/spy": "4.0.18",
"@vitest/utils": "4.0.18",
"es-module-lexer": "^1.7.0",
"expect-type": "^1.2.2",
"magic-string": "^0.30.21",
"obug": "^2.1.1",
"pathe": "^2.0.3",
"picomatch": "^4.0.3",
"std-env": "^3.10.0",
"tinybench": "^2.9.0",
"tinyexec": "^1.0.2",
"tinyglobby": "^0.2.15",
"tinyrainbow": "^3.0.3",
"vite": "^6.0.0 || ^7.0.0",
"why-is-node-running": "^2.3.0"
},
"bin": {
"vitest": "vitest.mjs"
},
"engines": {
"node": "^20.0.0 || ^22.0.0 || >=24.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.0.18",
"@vitest/browser-preview": "4.0.18",
"@vitest/browser-webdriverio": "4.0.18",
"@vitest/ui": "4.0.18",
"happy-dom": "*",
"jsdom": "*"
},
"peerDependenciesMeta": {
"@edge-runtime/vm": {
"optional": true
},
"@opentelemetry/api": {
"optional": true
},
"@types/node": {
"optional": true
},
"@vitest/browser-playwright": {
"optional": true
},
"@vitest/browser-preview": {
"optional": true
},
"@vitest/browser-webdriverio": {
"optional": true
},
"@vitest/ui": {
"optional": true
},
"happy-dom": {
"optional": true
},
"jsdom": {
"optional": true
}
}
},
"node_modules/@angular-devkit/build-angular/node_modules/vitest/node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-middleware": {
"version": "7.4.5",
"dev": true,
@ -2424,24 +2155,6 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@angular-devkit/schematics/node_modules/chokidar": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"readdirp": "^5.0.0"
},
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular-devkit/schematics/node_modules/cli-spinners": {
"version": "3.3.0",
"dev": true,
@ -2512,22 +2225,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@angular-devkit/schematics/node_modules/readdirp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular-devkit/schematics/node_modules/string-width": {
"version": "8.1.0",
"dev": true,
@ -2814,24 +2511,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@angular/cli/node_modules/chokidar": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"readdirp": "^5.0.0"
},
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular/cli/node_modules/cliui": {
"version": "9.0.1",
"dev": true,
@ -2858,22 +2537,6 @@
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/@angular/cli/node_modules/readdirp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@angular/cli/node_modules/string-width": {
"version": "7.2.0",
"dev": true,
@ -6432,7 +6095,6 @@
"os": [
"aix"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6450,7 +6112,6 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6468,7 +6129,6 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6486,7 +6146,6 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6504,7 +6163,6 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6522,7 +6180,6 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6540,7 +6197,6 @@
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6558,7 +6214,6 @@
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6576,7 +6231,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6594,7 +6248,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6612,7 +6265,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6630,7 +6282,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6648,7 +6299,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6666,7 +6316,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6684,7 +6333,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6702,7 +6350,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6718,7 +6365,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6736,7 +6382,6 @@
"os": [
"netbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6754,7 +6399,6 @@
"os": [
"netbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6772,7 +6416,6 @@
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6790,7 +6433,6 @@
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6808,7 +6450,6 @@
"os": [
"openharmony"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6826,7 +6467,6 @@
"os": [
"sunos"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6844,7 +6484,6 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6862,7 +6501,6 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -6880,7 +6518,6 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=18"
}
@ -11184,40 +10821,6 @@
}
}
},
"node_modules/@schematics/angular/node_modules/chokidar": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"readdirp": "^5.0.0"
},
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@schematics/angular/node_modules/readdirp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@sigstore/bundle": {
"version": "4.0.0",
"dev": true,
@ -13038,47 +12641,6 @@
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/mocker": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz",
"integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@vitest/spy": "4.0.18",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"msw": "^2.4.9",
"vite": "^6.0.0 || ^7.0.0-0"
},
"peerDependenciesMeta": {
"msw": {
"optional": true
},
"vite": {
"optional": true
}
}
},
"node_modules/@vitest/mocker/node_modules/@vitest/spy": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz",
"integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/pretty-format": {
"version": "3.2.4",
"dev": true,
@ -14911,6 +14473,15 @@
"url": "https://www.paypal.me/kirilvatev"
}
},
"node_modules/capacitor-plugin-safe-area": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/capacitor-plugin-safe-area/-/capacitor-plugin-safe-area-4.0.3.tgz",
"integrity": "sha512-RteQU+EcwfQFup66fOrlfYNfkGi3jSVwD8YimUAdyZlelriiqRctmKK5ONYXyt+Wd2e5y5F1A081kB9SRHGg2Q==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=7.0.0"
}
},
"node_modules/chai": {
"version": "5.3.3",
"dev": true,
@ -23922,19 +23493,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/obug": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
"integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
"dev": true,
"funding": [
"https://github.com/sponsors/sxzz",
"https://opencollective.com/debug"
],
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/on-exit-leak-free": {
"version": "2.1.2",
"license": "MIT",
@ -25033,7 +24591,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@ -29664,7 +29221,6 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}

View file

@ -148,6 +148,7 @@
"@capacitor/status-bar": "^7.0.4",
"@material-symbols/font-400": "^0.40.2",
"@noble/ciphers": "^2.1.1",
"capacitor-plugin-safe-area": "^4.0.3",
"electron-dl": "^3.5.2",
"electron-localshortcut": "^3.2.1",
"electron-log": "^5.4.3",

View file

@ -34,6 +34,7 @@ import { CapacitorPlatformService } from '../platform/capacitor-platform.service
import { Keyboard, KeyboardInfo } from '@capacitor/keyboard';
import { PluginListenerHandle } from '@capacitor/core';
import { StatusBar, Style } from '@capacitor/status-bar';
import { SafeArea } from 'capacitor-plugin-safe-area';
import { LS } from '../persistence/storage-keys.const';
import { CustomThemeService } from './custom-theme.service';
import { Log } from '../log';
@ -285,6 +286,7 @@ export class GlobalThemeService {
if (this._platformService.isNative) {
this.document.body.classList.add(BodyClass.isNativeMobile);
this._initMobileStatusBar();
this._initSafeAreaInsets();
if (this._platformService.isIOS()) {
this.document.body.classList.add(BodyClass.isIOS);
@ -507,6 +509,29 @@ export class GlobalThemeService {
* Initialize mobile status bar styling.
* Syncs status bar style with app dark/light mode on both iOS and Android.
*/
/**
* Read native safe area insets and set CSS variables.
* Works around Capacitor 7's broken adjustMarginsForEdgeToEdge and
* Android WebView's unreliable env(safe-area-inset-*) values.
*/
private _initSafeAreaInsets(): void {
const applyInsets = (insets: {
top: number;
right: number;
bottom: number;
left: number;
}): void => {
const root = this.document.documentElement;
root.style.setProperty('--safe-area-inset-top', `${insets.top}px`);
root.style.setProperty('--safe-area-inset-bottom', `${insets.bottom}px`);
root.style.setProperty('--safe-area-inset-left', `${insets.left}px`);
root.style.setProperty('--safe-area-inset-right', `${insets.right}px`);
};
SafeArea.getSafeAreaInsets().then(({ insets }) => applyInsets(insets));
SafeArea.addListener('safeAreaChanged', ({ insets }) => applyInsets(insets));
}
private _initMobileStatusBar(): void {
effect(() => {
const isDark = this.isDarkTheme();
@ -514,7 +539,7 @@ export class GlobalThemeService {
Log.warn('Failed to set status bar style', err);
});
if (this._platformService.isAndroid()) {
StatusBar.setBackgroundColor({ color: isDark ? '#131314' : '#ffffff' }).catch(
StatusBar.setBackgroundColor({ color: isDark ? '#131314' : '#f8f8f7' }).catch(
(err) => {
Log.warn('Failed to set status bar background color', err);
},

View file

@ -203,8 +203,15 @@ export class TaskContextMenuInnerComponent implements AfterViewInit {
if (ev instanceof MouseEvent || isTouchEventInstance(ev)) {
this.contextMenuPosition.x =
('touches' in ev ? ev.touches[0].clientX : ev.clientX) + 10 + 'px';
this.contextMenuPosition.y =
('touches' in ev ? ev.touches[0].clientY : ev.clientY) - 48 + 'px';
const rawY = ('touches' in ev ? ev.touches[0].clientY : ev.clientY) - 48;
const safeAreaTop =
parseInt(
getComputedStyle(document.documentElement).getPropertyValue(
'--safe-area-inset-top',
),
10,
) || 0;
this.contextMenuPosition.y = Math.max(rawY, safeAreaTop) + 'px';
}
this._isOpenedFromKeyboard = isOpenedFromKeyBoard;

View file

@ -89,8 +89,15 @@ export class ContextMenuComponent implements OnInit {
event.stopPropagation();
this.contextMenuPosition.x =
('touches' in event ? event.touches[0].clientX : event.clientX) + 'px';
this.contextMenuPosition.y =
('touches' in event ? event.touches[0].clientY : event.clientY) + 'px';
const rawY = 'touches' in event ? event.touches[0].clientY : event.clientY;
const safeAreaTop =
parseInt(
getComputedStyle(document.documentElement).getPropertyValue(
'--safe-area-inset-top',
),
10,
) || 0;
this.contextMenuPosition.y = Math.max(rawY, safeAreaTop) + 'px';
const contextMenuTriggerEl = this.contextMenuTriggerEl();
contextMenuTriggerEl.menuData = {
x: this.contextMenuPosition.x,