Handle Android back navigation in app
This commit is contained in:
@@ -9,6 +9,7 @@ android {
|
|||||||
|
|
||||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(':capacitor-app')
|
||||||
implementation project(':capacitor-push-notifications')
|
implementation project(':capacitor-push-notifications')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,8 @@
|
|||||||
include ':capacitor-android'
|
include ':capacitor-android'
|
||||||
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||||
|
|
||||||
|
include ':capacitor-app'
|
||||||
|
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
||||||
|
|
||||||
include ':capacitor-push-notifications'
|
include ':capacitor-push-notifications'
|
||||||
project(':capacitor-push-notifications').projectDir = new File('../node_modules/@capacitor/push-notifications/android')
|
project(':capacitor-push-notifications').projectDir = new File('../node_modules/@capacitor/push-notifications/android')
|
||||||
|
|||||||
Generated
+10
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "6.2.1",
|
"@capacitor/android": "6.2.1",
|
||||||
|
"@capacitor/app": "^6.0.2",
|
||||||
"@capacitor/core": "6.2.1",
|
"@capacitor/core": "6.2.1",
|
||||||
"@capacitor/push-notifications": "6.0.5"
|
"@capacitor/push-notifications": "6.0.5"
|
||||||
},
|
},
|
||||||
@@ -25,6 +26,15 @@
|
|||||||
"@capacitor/core": "^6.2.0"
|
"@capacitor/core": "^6.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@capacitor/app": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@capacitor/app/-/app-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-SiGTGgslK4TbWJVImCUL1odul7/YFkVfkYtAYS9AAEzQpxBECBeRnuN3FFBcfZ9eiN1XxFBFchhiwpxtx/c7yQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@capacitor/core": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@capacitor/cli": {
|
"node_modules/@capacitor/cli": {
|
||||||
"version": "6.2.1",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-6.2.1.tgz",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "6.2.1",
|
"@capacitor/android": "6.2.1",
|
||||||
|
"@capacitor/app": "^6.0.2",
|
||||||
"@capacitor/core": "6.2.1",
|
"@capacitor/core": "6.2.1",
|
||||||
"@capacitor/push-notifications": "6.0.5"
|
"@capacitor/push-notifications": "6.0.5"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/* Keep Android back navigation inside MetalCircle until the calendar home. */
|
||||||
|
(() => {
|
||||||
|
const capacitor = window.Capacitor;
|
||||||
|
if (!capacitor || capacitor.getPlatform() !== 'android' || typeof capacitor.addListener !== 'function') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
capacitor.addListener('App', 'backButton', ({canGoBack}) => {
|
||||||
|
const openDialog = document.querySelector('dialog[open]');
|
||||||
|
if (openDialog) {
|
||||||
|
openDialog.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const openDetails = document.querySelector('details[open]');
|
||||||
|
if (openDetails) {
|
||||||
|
openDetails.open = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location.pathname === '/') {
|
||||||
|
capacitor.nativePromise('App', 'exitApp', {}).catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canGoBack) {
|
||||||
|
history.back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
location.replace('/');
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
// The website remains usable when the native App plugin is unavailable.
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -12,3 +12,4 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</details>
|
</details>
|
||||||
<script src="/static/js/header-controls.js" defer></script>
|
<script src="/static/js/header-controls.js" defer></script>
|
||||||
|
<script src="/static/js/android-back-button.js" defer></script>
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const test = require('node:test');
|
||||||
|
const vm = require('node:vm');
|
||||||
|
|
||||||
|
const script = fs.readFileSync(
|
||||||
|
new URL('../static/js/android-back-button.js', `file://${__filename}`),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
|
||||||
|
function createHarness({platform = 'android', pathname = '/', openDialog = false, openDetails = false} = {}) {
|
||||||
|
const calls = [];
|
||||||
|
const dialog = {close: () => calls.push('dialog.close')};
|
||||||
|
const details = {open: true};
|
||||||
|
const capacitor = {
|
||||||
|
getPlatform: () => platform,
|
||||||
|
addListener: (plugin, event, handler) => {
|
||||||
|
calls.push(`listener:${plugin}:${event}`);
|
||||||
|
capacitor.backHandler = handler;
|
||||||
|
return {remove: async () => {}};
|
||||||
|
},
|
||||||
|
nativePromise: (plugin, method) => {
|
||||||
|
calls.push(`${plugin}.${method}`);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.runInNewContext(script, {
|
||||||
|
window: {Capacitor: capacitor},
|
||||||
|
document: {
|
||||||
|
querySelector: selector => selector === 'dialog[open]'
|
||||||
|
? (openDialog ? dialog : null)
|
||||||
|
: (openDetails ? details : null)
|
||||||
|
},
|
||||||
|
location: {pathname, replace: path => calls.push(`replace:${path}`)},
|
||||||
|
history: {back: () => calls.push('history.back')}
|
||||||
|
});
|
||||||
|
return {calls, capacitor, details};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Android back closes an open dialog or details panel first', () => {
|
||||||
|
const dialog = createHarness({openDialog: true});
|
||||||
|
dialog.capacitor.backHandler({canGoBack: true});
|
||||||
|
assert.deepEqual(dialog.calls, ['listener:App:backButton', 'dialog.close']);
|
||||||
|
|
||||||
|
const details = createHarness({openDetails: true});
|
||||||
|
details.capacitor.backHandler({canGoBack: true});
|
||||||
|
assert.equal(details.details.open, false);
|
||||||
|
assert.deepEqual(details.calls, ['listener:App:backButton']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Android back follows WebView history away from the start page', () => {
|
||||||
|
const {calls, capacitor} = createHarness({pathname: '/messages'});
|
||||||
|
capacitor.backHandler({canGoBack: true});
|
||||||
|
assert.deepEqual(calls, ['listener:App:backButton', 'history.back']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Android back returns to the start page when a deep link has no history', () => {
|
||||||
|
const {calls, capacitor} = createHarness({pathname: '/messages/42'});
|
||||||
|
capacitor.backHandler({canGoBack: false});
|
||||||
|
assert.deepEqual(calls, ['listener:App:backButton', 'replace:/']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Android back exits only from the start page', () => {
|
||||||
|
const {calls, capacitor} = createHarness();
|
||||||
|
capacitor.backHandler({canGoBack: false});
|
||||||
|
assert.deepEqual(calls, ['listener:App:backButton', 'App.exitApp']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('web browsers do not register the native back handler', () => {
|
||||||
|
const {calls} = createHarness({platform: 'web'});
|
||||||
|
assert.deepEqual(calls, []);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user