fix(ios): PERMANENT native safe-area + keyboard via Capacitor 7 + safe-area plugin
Device screenshots proved the WebView reports env(safe-area-inset-*)=0, so no web/CSS/JS trick can move content off the Dynamic Island — it must be fixed natively. Upgrade the Capacitor shell to v7 and add @capacitor-community/safe-area (populates env() with real per-device insets, edge-to-edge) and @capacitor/keyboard (resize:native so the composer sits directly on the keyboard, like Teams). - mobile: Capacitor 6 -> 7 for all plugins; add safe-area + keyboard; SafeArea/Keyboard config. - codemagic: npm ci -> npm install (dependency set changed); drop stale Cap6 lockfile. - web: remove the temporary StatusBar/34px hacks (env() now works); keep var(--sat)/var(--sab) padding. - share/connect: back link now respects env(safe-area-inset-top/left). - add native-style slide in/out (push/pop) animation when opening/closing a chat on mobile. Build marker -> 2026-07-17-batch92. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+3
-1
@@ -36,7 +36,9 @@ workflows:
|
||||
- name: Install JS dependencies
|
||||
script: |
|
||||
cd mobile
|
||||
npm ci
|
||||
# npm install (not ci): the dependency set changed to Capacitor 7 + the safe-area/keyboard
|
||||
# plugins, so we let npm resolve a fresh tree rather than require a pre-synced lockfile.
|
||||
npm install
|
||||
|
||||
- name: Generate the iOS project (Capacitor)
|
||||
script: |
|
||||
|
||||
@@ -10,6 +10,14 @@
|
||||
"plugins": {
|
||||
"PushNotifications": {
|
||||
"presentationOptions": ["badge", "sound", "alert"]
|
||||
},
|
||||
"SafeArea": {
|
||||
"detectViewportFitCoverChanges": true,
|
||||
"initialViewportFitCover": true,
|
||||
"offsetForKeyboardInsetBug": true
|
||||
},
|
||||
"Keyboard": {
|
||||
"resize": "native"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
-4929
File diff suppressed because it is too large
Load Diff
+11
-9
@@ -10,17 +10,19 @@
|
||||
"ios": "cap open ios"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^6.1.0",
|
||||
"@capacitor/app": "^6.0.0",
|
||||
"@capacitor/camera": "^6.0.0",
|
||||
"@capacitor/core": "^6.1.0",
|
||||
"@capacitor/ios": "^6.1.0",
|
||||
"@capacitor/push-notifications": "^6.0.0",
|
||||
"@capacitor/splash-screen": "^6.0.0",
|
||||
"@capacitor/status-bar": "^6.0.0"
|
||||
"@capacitor-community/safe-area": "^7.0.0",
|
||||
"@capacitor/android": "^7.0.0",
|
||||
"@capacitor/app": "^7.0.0",
|
||||
"@capacitor/camera": "^7.0.0",
|
||||
"@capacitor/core": "^7.0.0",
|
||||
"@capacitor/ios": "^7.0.0",
|
||||
"@capacitor/keyboard": "^7.0.0",
|
||||
"@capacitor/push-notifications": "^7.0.0",
|
||||
"@capacitor/splash-screen": "^7.0.0",
|
||||
"@capacitor/status-bar": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/assets": "^3.0.5",
|
||||
"@capacitor/cli": "^6.1.0"
|
||||
"@capacitor/cli": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,6 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>Biz Connect — Agent Console</title>
|
||||
<script>
|
||||
// Native shell: push content below the status bar/notch (the Capacitor WebView reports safe-area insets
|
||||
// as 0, so the back button/header would otherwise slide under the Dynamic Island).
|
||||
(function(){ try{ var C=window.Capacitor; if(!C||!C.isNativePlatform||!C.isNativePlatform()) return;
|
||||
var p=(C.getPlatform&&C.getPlatform())||''; var r=document.documentElement; r.classList.add('native-app'); if(p) r.classList.add('native-'+p);
|
||||
var SB=C.Plugins&&C.Plugins.StatusBar; if(SB){ try{ SB.setOverlaysWebView({overlay:false}); }catch(_){}; try{ SB.setStyle({style:'DARK'}); }catch(_){}; try{ SB.setBackgroundColor({color:'#ffffff'}); }catch(_){} }
|
||||
}catch(_){} })();
|
||||
</script>
|
||||
<meta name="theme-color" content="#1F3B73">
|
||||
<link rel="icon" href="/favicon.ico?v=3" sizes="any">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png?v=3">
|
||||
@@ -71,7 +63,7 @@
|
||||
.pwwrap input{padding-right:2.7rem;}
|
||||
.eye{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);background:none;border:none;padding:.3rem;width:auto;color:var(--muted);display:inline-flex;align-items:center;cursor:pointer;margin:0;}
|
||||
.eye:hover{color:var(--blue);}
|
||||
#homeLink{position:fixed;top:14px;left:16px;z-index:50;display:inline-flex;align-items:center;gap:6px;background:rgba(255,255,255,.92);color:#1F3B73;text-decoration:none;font-weight:600;font-size:.86rem;padding:.45rem .8rem;border-radius:10px;box-shadow:0 4px 12px rgba(0,0,0,.15);}
|
||||
#homeLink{position:fixed;top:calc(14px + env(safe-area-inset-top,0px));left:calc(16px + env(safe-area-inset-left,0px));z-index:50;display:inline-flex;align-items:center;gap:6px;background:rgba(255,255,255,.92);color:#1F3B73;text-decoration:none;font-weight:600;font-size:.86rem;padding:.45rem .8rem;border-radius:10px;box-shadow:0 4px 12px rgba(0,0,0,.15);}
|
||||
.formerr{color:#b91c1c;font-weight:600;font-size:.88rem;margin-top:.9rem;min-height:1.1em;text-align:left;}
|
||||
.formerr.show{display:flex;align-items:center;gap:.5rem;background:#fee2e2;border:1px solid #fca5a5;border-radius:9px;padding:.6rem .75rem;animation:errShake .35s;}
|
||||
.formerr.show::before{content:"⚠";font-size:1rem;}
|
||||
|
||||
+25
-16
@@ -23,11 +23,10 @@
|
||||
bottom rail (the "gap"). overscroll-behavior:none pins the page; the rail's own height already
|
||||
includes the home-indicator inset. */
|
||||
html,body{height:100%;overscroll-behavior:none;}
|
||||
/* Safe-area insets. The iOS Capacitor WebView reports env(safe-area-inset-*)=0, so on the native app
|
||||
we (a) push content below the status bar/notch with StatusBar.setOverlaysWebView(false) — see the
|
||||
native-chrome script — and (b) hard-code the bottom home-indicator inset here (env() can't give it). */
|
||||
/* Safe-area insets. The native app enables edge-to-edge via the @capacitor-community/safe-area plugin,
|
||||
which populates env(safe-area-inset-*) with real values on every device (notch, Dynamic Island, home
|
||||
indicator). On the web these resolve to 0. All layout below pads with var(--sat)/var(--sab). */
|
||||
:root{--sat:env(safe-area-inset-top,0px);--sab:env(safe-area-inset-bottom,0px);}
|
||||
html.native-ios{--sab:34px;}
|
||||
body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--ink);margin:0;display:flex;flex-direction:column;height:100vh;height:100dvh;overflow:hidden;position:fixed;inset:0;width:100%;}
|
||||
|
||||
/* ---- Top bar ---- */
|
||||
@@ -1069,6 +1068,13 @@
|
||||
then only needs to clear the home indicator. */
|
||||
body.chat-open .rail{display:none;}
|
||||
body.chat-open .content .panel{bottom:var(--sab);}
|
||||
/* Native-style push/pop: the conversation slides in from the right when opened and slides back out
|
||||
when you go back, so navigation has a clear direction (like Teams/WhatsApp). */
|
||||
@keyframes bzConvoIn{from{transform:translateX(100%);}to{transform:translateX(0);}}
|
||||
@keyframes bzConvoOut{from{transform:translateX(0);}to{transform:translateX(100%);}}
|
||||
body.chat-open:not(.chat-closing) .content .panel{animation:bzConvoIn .22s cubic-bezier(.2,.7,.3,1);}
|
||||
body.chat-closing .content{display:block!important;}
|
||||
body.chat-closing .content .panel{animation:bzConvoOut .2s cubic-bezier(.4,0,.7,.4) forwards;will-change:transform;}
|
||||
/* Bigger touch targets */
|
||||
.chat-row{padding:.7rem .85rem;}
|
||||
.convo-head{padding:.7rem .85rem;}
|
||||
@@ -1095,7 +1101,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<script src="/icons.js?v=6"></script>
|
||||
<script>window.__BUILD='2026-07-17-batch91';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||
<script>window.__BUILD='2026-07-17-batch92';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||
// Emoji are rendered with the OS's own (colour) emoji font — instant, zero network.
|
||||
//
|
||||
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
|
||||
@@ -1105,16 +1111,11 @@
|
||||
// existing call sites don't have to change.
|
||||
function twemojify(_el){ /* native emoji: nothing to do */ }</script>
|
||||
<script>
|
||||
// Native shell chrome (iOS/Android). The Capacitor WebView reports env(safe-area-inset-*)=0, so content
|
||||
// slides under the Dynamic Island / notch. We tag <html> (CSS then hard-codes the bottom home-indicator
|
||||
// inset) and ask the StatusBar plugin to NOT overlay the WebView, which pushes the top of our content
|
||||
// below the status bar. Runs on every entry page (home/share/connect/index) that includes this snippet.
|
||||
(function(){ try{
|
||||
var C=window.Capacitor; if(!C||!C.isNativePlatform||!C.isNativePlatform()) return;
|
||||
var p=(C.getPlatform&&C.getPlatform())||''; var r=document.documentElement;
|
||||
r.classList.add('native-app'); if(p) r.classList.add('native-'+p);
|
||||
var SB=C.Plugins&&C.Plugins.StatusBar;
|
||||
if(SB){ try{ SB.setOverlaysWebView({overlay:false}); }catch(_){}; try{ SB.setStyle({style:'DARK'}); }catch(_){}; try{ SB.setBackgroundColor({color:'#ffffff'}); }catch(_){} }
|
||||
// Native shell tag (iOS/Android). Safe-area insets are handled natively by the @capacitor-community/
|
||||
// safe-area plugin (it makes env(safe-area-inset-*) report real values), so there's no JS status-bar
|
||||
// juggling here — we only tag <html> so CSS can make small native-only tweaks if needed.
|
||||
(function(){ try{ var C=window.Capacitor; if(!C||!C.isNativePlatform||!C.isNativePlatform()) return;
|
||||
var p=(C.getPlatform&&C.getPlatform())||''; var r=document.documentElement; r.classList.add('native-app'); if(p) r.classList.add('native-'+p);
|
||||
}catch(_){} })();
|
||||
</script>
|
||||
<div class="loading" id="loading"><div class="ld-inner"><img src="/loaders/loader-orbit-dark.svg" alt="" width="72" height="72"><div class="ld-wm">Biz <b>Connect</b></div><span class="ld-sub">Loading…</span></div></div>
|
||||
@@ -4613,7 +4614,15 @@ function switchTab(tab){
|
||||
if(tab==='connect' && !loaded.connect){ document.getElementById('connectPanel').innerHTML='<iframe src="/connect?embed=1" allow="camera; microphone; display-capture; clipboard-read; clipboard-write"></iframe>'; loaded.connect=true; }
|
||||
if(tab==='meeting' && meetState==='idle'){ renderMeetingLobby(); }
|
||||
}
|
||||
function showWelcome(){ selected=null; document.body.classList.remove('chat-open'); renderChats(searchVal()); renderChatPanel(); updateRailUnread(); }
|
||||
function showWelcome(){
|
||||
const finish=()=>{ selected=null; document.body.classList.remove('chat-open','chat-closing'); renderChats(searchVal()); renderChatPanel(); updateRailUnread(); };
|
||||
// Mobile: play the slide-out (bzConvoOut) before swapping back to the chat list, so "back" has a
|
||||
// clear push/pop direction. Desktop just closes instantly.
|
||||
if(document.body.classList.contains('chat-open') && !document.body.classList.contains('chat-closing') && window.matchMedia('(max-width:760px)').matches){
|
||||
document.body.classList.add('chat-closing');
|
||||
setTimeout(finish, 200);
|
||||
} else finish();
|
||||
}
|
||||
railBtns.forEach(btn=>{ btn.onclick=()=>{
|
||||
const tab=btn.dataset.tab;
|
||||
// Re-clicking Chat (while already on it) returns to the welcome screen.
|
||||
|
||||
@@ -3,15 +3,6 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>Biz Connect</title>
|
||||
<script>
|
||||
// Native shell: push content below the status bar/notch (the Capacitor WebView reports safe-area insets
|
||||
// as 0). Runs before render so headers never sit under the Dynamic Island.
|
||||
(function(){ try{ var C=window.Capacitor; if(!C||!C.isNativePlatform||!C.isNativePlatform()) return;
|
||||
var p=(C.getPlatform&&C.getPlatform())||''; var r=document.documentElement; r.classList.add('native-app'); if(p) r.classList.add('native-'+p);
|
||||
var SB=C.Plugins&&C.Plugins.StatusBar; if(SB){ try{ SB.setOverlaysWebView({overlay:false}); }catch(_){}; try{ SB.setStyle({style:'DARK'}); }catch(_){}; try{ SB.setBackgroundColor({color:'#ffffff'}); }catch(_){} }
|
||||
}catch(_){} })();
|
||||
</script>
|
||||
<meta name="theme-color" content="#1F3B73">
|
||||
<link rel="icon" href="/favicon.ico?v=3" sizes="any">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png?v=3">
|
||||
|
||||
@@ -4,14 +4,6 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>Biz Connect — Share your screen</title>
|
||||
<script>
|
||||
// Native shell: push content below the status bar/notch (the Capacitor WebView reports safe-area insets
|
||||
// as 0, so the back button/header would otherwise slide under the Dynamic Island).
|
||||
(function(){ try{ var C=window.Capacitor; if(!C||!C.isNativePlatform||!C.isNativePlatform()) return;
|
||||
var p=(C.getPlatform&&C.getPlatform())||''; var r=document.documentElement; r.classList.add('native-app'); if(p) r.classList.add('native-'+p);
|
||||
var SB=C.Plugins&&C.Plugins.StatusBar; if(SB){ try{ SB.setOverlaysWebView({overlay:false}); }catch(_){}; try{ SB.setStyle({style:'DARK'}); }catch(_){}; try{ SB.setBackgroundColor({color:'#ffffff'}); }catch(_){} }
|
||||
}catch(_){} })();
|
||||
</script>
|
||||
<meta name="theme-color" content="#1F3B73">
|
||||
<link rel="icon" href="/favicon.ico?v=3" sizes="any">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png?v=3">
|
||||
@@ -85,7 +77,7 @@
|
||||
<body>
|
||||
<script>if(new URLSearchParams(location.search).get('embed')==='1')document.documentElement.classList.add('embed');</script>
|
||||
<div class="indicator" id="indicator">● Your screen is being shared — close this tab anytime to stop</div>
|
||||
<a href="/" id="homeLink" style="position:fixed;top:14px;left:16px;z-index:50;display:inline-flex;align-items:center;gap:6px;background:rgba(255,255,255,.92);color:#1F3B73;text-decoration:none;font-weight:600;font-size:.86rem;padding:.45rem .8rem;border-radius:10px;box-shadow:0 4px 12px rgba(0,0,0,.15)"><span data-ic="arrowLeft" data-sz="16"></span> Home</a>
|
||||
<a href="/" id="homeLink" style="position:fixed;top:calc(14px + env(safe-area-inset-top,0px));left:calc(16px + env(safe-area-inset-left,0px));z-index:50;display:inline-flex;align-items:center;gap:6px;background:rgba(255,255,255,.92);color:#1F3B73;text-decoration:none;font-weight:600;font-size:.86rem;padding:.45rem .8rem;border-radius:10px;box-shadow:0 4px 12px rgba(0,0,0,.15)"><span data-ic="arrowLeft" data-sz="16"></span> Home</a>
|
||||
<div class="stage">
|
||||
<div class="brandpanel">
|
||||
<img src="/icon-512.png" alt="" style="width:88px;height:88px;border-radius:22px;object-fit:contain;margin-bottom:1.2rem;background:#fff;padding:8px;box-shadow:0 12px 30px rgba(0,0,0,.25)" onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'mark',textContent:'B'}))">
|
||||
|
||||
Reference in New Issue
Block a user