feat(pos): Capacitor mobile app setup — Android + iOS wrapper
Set up Capacitor to wrap the Nexus POS web app as a native mobile app. The app loads from the remote server URL so no bundling is needed. Includes native-bridge.js for camera, push notifications, haptics, and status bar integration when running inside the native shell. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
4
pos/.gitignore
vendored
Normal file
4
pos/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
android/
|
||||||
|
ios/
|
||||||
|
.gradle/
|
||||||
17
pos/capacitor.config.json
Normal file
17
pos/capacitor.config.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"appId": "com.nexusautoparts.pos",
|
||||||
|
"appName": "Nexus POS",
|
||||||
|
"webDir": "www",
|
||||||
|
"server": {
|
||||||
|
"url": "https://nexus.consultoria-as.com/pos",
|
||||||
|
"cleartext": true
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"SplashScreen": {
|
||||||
|
"launchShowDuration": 2000,
|
||||||
|
"backgroundColor": "#0d0d0d",
|
||||||
|
"showSpinner": true,
|
||||||
|
"spinnerColor": "#F5A623"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
pos/mobile/README.md
Normal file
50
pos/mobile/README.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Nexus POS — Mobile App Build Guide
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- Node.js 18+
|
||||||
|
- Android Studio (for Android)
|
||||||
|
- Xcode 15+ (for iOS, macOS only)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The Capacitor app loads the POS from the remote server at
|
||||||
|
`https://nexus.consultoria-as.com/pos`. This means:
|
||||||
|
- The app requires internet on first load.
|
||||||
|
- The PWA service worker handles offline caching after that.
|
||||||
|
- No HTML/JS/CSS is bundled into the native binary.
|
||||||
|
|
||||||
|
## Android Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/Autopartes/pos
|
||||||
|
npx cap sync android
|
||||||
|
npx cap open android
|
||||||
|
# In Android Studio: Build > Generate Signed APK
|
||||||
|
```
|
||||||
|
|
||||||
|
## iOS Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/Autopartes/pos
|
||||||
|
npx cap sync ios
|
||||||
|
npx cap open ios
|
||||||
|
# In Xcode: Product > Archive
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx cap run android --livereload --external
|
||||||
|
npx cap run ios --livereload --external
|
||||||
|
```
|
||||||
|
|
||||||
|
## App Icons & Splash Screens
|
||||||
|
|
||||||
|
Place source images in `mobile/resources/`:
|
||||||
|
- `icon.png` — 1024x1024 app icon
|
||||||
|
- `splash.png` — 2732x2732 splash screen
|
||||||
|
|
||||||
|
Then generate platform assets:
|
||||||
|
```bash
|
||||||
|
npx @capacitor/assets generate
|
||||||
|
```
|
||||||
BIN
pos/mobile/resources/icon.png
Normal file
BIN
pos/mobile/resources/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
1135
pos/package-lock.json
generated
Normal file
1135
pos/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
pos/package.json
Normal file
24
pos/package.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "pos",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"@capacitor/android": "^8.3.0",
|
||||||
|
"@capacitor/camera": "^8.0.2",
|
||||||
|
"@capacitor/cli": "^7.6.1",
|
||||||
|
"@capacitor/core": "^8.3.0",
|
||||||
|
"@capacitor/haptics": "^8.0.2",
|
||||||
|
"@capacitor/ios": "^8.3.0",
|
||||||
|
"@capacitor/push-notifications": "^8.0.3",
|
||||||
|
"@capacitor/splash-screen": "^8.0.1",
|
||||||
|
"@capacitor/status-bar": "^8.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
72
pos/static/js/native-bridge.js
Normal file
72
pos/static/js/native-bridge.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// native-bridge.js — Detects if running inside Capacitor native app
|
||||||
|
// and provides native API access (camera for barcode, push notifications, haptics)
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
window.NexusNative = {
|
||||||
|
isNative: typeof Capacitor !== 'undefined',
|
||||||
|
|
||||||
|
// Camera for barcode scanning
|
||||||
|
async scanBarcode() {
|
||||||
|
if (!this.isNative) return null;
|
||||||
|
try {
|
||||||
|
const { Camera } = await import('@capacitor/camera');
|
||||||
|
const photo = await Camera.getPhoto({
|
||||||
|
quality: 90,
|
||||||
|
resultType: 'base64'
|
||||||
|
});
|
||||||
|
// In production, send to a barcode decode service
|
||||||
|
return photo;
|
||||||
|
} catch(e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Push notification registration
|
||||||
|
async registerPush() {
|
||||||
|
if (!this.isNative) return null;
|
||||||
|
try {
|
||||||
|
const { PushNotifications } = await import('@capacitor/push-notifications');
|
||||||
|
const result = await PushNotifications.requestPermissions();
|
||||||
|
if (result.receive === 'granted') {
|
||||||
|
await PushNotifications.register();
|
||||||
|
}
|
||||||
|
PushNotifications.addListener('registration', token => {
|
||||||
|
console.log('Push token:', token.value);
|
||||||
|
// Send token to server for this employee
|
||||||
|
});
|
||||||
|
PushNotifications.addListener('pushNotificationReceived', notification => {
|
||||||
|
console.log('Push received:', notification);
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
console.log('Push not available:', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Haptic feedback
|
||||||
|
async vibrate() {
|
||||||
|
if (!this.isNative) return;
|
||||||
|
try {
|
||||||
|
const { Haptics, ImpactStyle } = await import('@capacitor/haptics');
|
||||||
|
await Haptics.impact({ style: ImpactStyle.Light });
|
||||||
|
} catch(e) {}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Status bar (hide for fullscreen POS)
|
||||||
|
async setupStatusBar() {
|
||||||
|
if (!this.isNative) return;
|
||||||
|
try {
|
||||||
|
const { StatusBar, Style } = await import('@capacitor/status-bar');
|
||||||
|
await StatusBar.setStyle({ style: Style.Dark });
|
||||||
|
await StatusBar.setBackgroundColor({ color: '#0d0d0d' });
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auto-init if native
|
||||||
|
if (window.NexusNative.isNative) {
|
||||||
|
window.NexusNative.setupStatusBar();
|
||||||
|
window.NexusNative.registerPush();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
<link rel="stylesheet" href="/pos/static/css/onboarding.css" />
|
<link rel="stylesheet" href="/pos/static/css/onboarding.css" />
|
||||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||||
<meta name="theme-color" content="#F5A623" />
|
<meta name="theme-color" content="#F5A623" />
|
||||||
|
<script src="/pos/static/js/native-bridge.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||||
<meta name="theme-color" content="#F5A623" />
|
<meta name="theme-color" content="#F5A623" />
|
||||||
|
<script src="/pos/static/js/native-bridge.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* =====================================================================
|
/* =====================================================================
|
||||||
|
|||||||
17
pos/www/index.html
Normal file
17
pos/www/index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Nexus POS</title>
|
||||||
|
<style>
|
||||||
|
body { background: #0d0d0d; color: #F5A623; font-family: sans-serif;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
height: 100vh; margin: 0; }
|
||||||
|
p { text-align: center; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Cargando Nexus POS…<br><small>Conectando al servidor</small></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user