gigafibre-fsm/apps/field-tech/src/main.js
louispaulb aa108ab13f feat(field-tech): app Capacitor native (geofence Transistorsoft + scan MLKit) + CI
App technicien : appairage 1x (QR), géorepérage natif en arrière-plan (app fermée)
-> checkpoints au hub /field/ts, scan série/MAC on-device MLKit. UI réutilisée depuis
le hub (/field). APK Android buildé (debug, 19 Mo, arm64-v8a + armeabi-v7a).

- apps/field-tech : Capacitor 6 + Vite ; src/main.js (appairage @capacitor/preferences
  + BackgroundGeolocation.addGeofences + redirection /field) ; projet android/ avec les
  4 correctifs Gradle commités (force work-runtime 2.9.1, minSdk 24, repos maven xms.g
  + Huawei, googlePlayServicesLocationVersion 21.0.1) ; abiFilters arm (APK 32->19 Mo).
- README : recette de build complète (toolchain M4, les 4 fixes, install/appairage) ;
  BUILD-ANDROID.md (Docker + Android Studio + release signée) ; CI-SETUP.md (runner Gitea).
- CI Gitea Actions (.gitea/workflows) : android (runner Linux HORS prod, cache Gradle/npm
  -> artefact APK) + ios (runner macOS, workflow_dispatch, en attente compte Apple).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:47:27 -04:00

59 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Targo Tech — app native (Capacitor + Vite).
// Appairage 1× (QR → token tech persistant) → géorepérage natif Transistorsoft (arrière-plan, app fermée)
// → POST auto des événements geofence au hub (/field/ts) → UI réutilisée depuis le hub (/field).
import BackgroundGeolocation from '@transistorsoft/capacitor-background-geolocation'
import { BarcodeScanner } from '@capacitor-mlkit/barcode-scanning'
import { Preferences } from '@capacitor/preferences'
const HUB = 'https://msg.gigafibre.ca'
const $ = (id) => document.getElementById(id)
const getToken = async () => (await Preferences.get({ key: 'tech_token' })).value
const setToken = async (t) => Preferences.set({ key: 'tech_token', value: t })
function tokenFromUrl (u) { try { return new URL(u).searchParams.get('t') } catch (e) { const m = /[?&]t=([^&\s]+)/.exec(u || ''); return m ? decodeURIComponent(m[1]) : (u && u.includes('.') ? u : null) } }
window.pairScan = async () => {
try {
const { barcodes } = await BarcodeScanner.scan()
const v = barcodes && barcodes[0] && (barcodes[0].rawValue || barcodes[0].displayValue)
const t = v && tokenFromUrl(v)
if (t) { await setToken(t); boot() } else $('msg').textContent = 'QR non reconnu'
} catch (e) { $('msg').textContent = 'Scan annulé' }
}
window.pairPaste = async () => { const t = tokenFromUrl($('lnk').value.trim()); if (t) { await setToken(t); boot() } else $('msg').textContent = 'Lien invalide' }
window.unpair = async () => { await Preferences.remove({ key: 'tech_token' }); location.reload() }
// Géorepérage natif : enregistre les jobs du jour comme geofences ; Transistorsoft POSTe enter/exit au hub (autoSync),
// même app fermée. L'identifier de chaque geofence = le token signé du job → le hub mappe + écrit le checkpoint.
async function initGeo (token) {
await BackgroundGeolocation.ready({
desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH,
distanceFilter: 50,
stopOnTerminate: false,
startOnBoot: true,
geofenceModeHighAccuracy: true,
locationAuthorizationRequest: 'Always',
backgroundPermissionRationale: { title: 'Suivi des interventions', message: 'Targo Tech a besoin de la localisation « toujours » pour détecter automatiquement vos arrivées/départs.' },
url: HUB + '/field/ts',
autoSync: true,
batchSync: false,
notification: { title: 'Targo Tech', text: 'Suivi des interventions actif' },
})
try {
const jobs = await fetch(HUB + '/field/tech?t=' + encodeURIComponent(token)).then(r => r.json()).then(j => j.jobs || [])
await BackgroundGeolocation.removeGeofences()
const fences = jobs.filter(j => j.lat && Math.abs(+j.lat) > 0.01).map(j => ({ identifier: j.token, latitude: +j.lat, longitude: +j.lon, radius: 200, notifyOnEntry: true, notifyOnExit: true }))
if (fences.length) await BackgroundGeolocation.addGeofences(fences)
} catch (e) { console.warn('geofences', e) }
const st = await BackgroundGeolocation.getState()
if (!st.enabled) await BackgroundGeolocation.start()
}
async function boot () {
const token = await getToken()
if (!token) { $('splash').style.display = 'none'; $('pair').style.display = 'block'; return }
try { await initGeo(token) } catch (e) { console.warn('initGeo', e) }
// Charge l'UI complète hébergée (liste/détail/carte/photo/scan IA + MLKit injecté par Capacitor)
location.replace(HUB + '/field?t=' + encodeURIComponent(token))
}
boot()