Live location sharing (botovod) + SMS 'я тут' with Yandex Maps link
Feature 1 - opt-in live location: - set_live_config Rust command writes live_share.json (url/token stay on device) - LiveShare.kt singleton posts the current fix to /api/live/push every ~10s from the GPS flush loop, on a background thread; diagnostics in live_share_log.jsonl - profile toggle + endpoint/token fields, off by default; server side is in botovod Feature 2 - SMS 'я тут': - send_sms / pick_contact / get_contact_result Rust commands - MainActivity: system contact picker (no READ_CONTACTS), direct SmsManager send when SEND_SMS is granted else the prefilled system composer; SEND_SMS in the manifest - contact config in profile (manual + picker), 'СМС' button on the record screen with an in-app confirm; text carries coords + a Yandex Maps pin link README + session log updated.
This commit is contained in:
parent
762221d186
commit
1410062d85
8 changed files with 415 additions and 1 deletions
|
|
@ -123,6 +123,15 @@ async function init() {
|
|||
window.__TAURI__?.core?.invoke("set_hydration_config",
|
||||
{ enabled: on, alertMl, sweatMlPerHour: 600, reAlertMin: 15, startDeficitMl: 0 });
|
||||
} catch (e) {}
|
||||
// Live location sharing is OFF by default — keep the native config file in sync with the UI
|
||||
// toggle so a session always sees the current choice (off unless the user turned it on).
|
||||
try {
|
||||
let liveOn = localStorage.getItem("baldav_live_on") === "1";
|
||||
let liveUrl = localStorage.getItem("baldav_live_url") || "https://4aiman.top/api/live/push";
|
||||
let liveToken = localStorage.getItem("baldav_live_token") || "";
|
||||
window.__TAURI__?.core?.invoke("set_live_config",
|
||||
{ enabled: liveOn, url: liveUrl, token: liveToken });
|
||||
} catch (e) {}
|
||||
if (typeof DB === "undefined" || typeof DB.getFullProfile !== "function") {
|
||||
app.innerHTML = `<div class="loading" style="color:#cf6679">
|
||||
Ошибка: db.js не загрузился.<br>
|
||||
|
|
@ -4702,6 +4711,29 @@ function renderWorkoutInline(container) {
|
|||
};
|
||||
}
|
||||
|
||||
// Send an SMS with the current position and a Yandex Maps pin link. Direct send if the system
|
||||
// granted SEND_SMS (the in-app confirm is the only tap), otherwise the native side opens the SMS
|
||||
// app prefilled. Works over 2G/EDGE with no internet — it is a plain text message.
|
||||
async function sendLocationSms() {
|
||||
let number = (localStorage.getItem("baldav_sms_number") || "").trim();
|
||||
if (!number) { appAlert("Сначала укажи номер в профиле"); return; }
|
||||
let pts = GPS.recording.points || [];
|
||||
let last = GPS.recording.lastMovingPoint || pts[pts.length - 1];
|
||||
if (!last || last.lat == null) { appAlert("Нет координат — подожди фикс GPS"); return; }
|
||||
let lat = (+last.lat).toFixed(5), lon = (+last.lon).toFixed(5);
|
||||
// Yandex Maps: pt=lon,lat drops a pin at the position
|
||||
let link = `https://yandex.ru/maps/?pt=${lon},${lat}&z=16&l=map`;
|
||||
let text = `Я тут: ${lat},${lon} ${link}`;
|
||||
let esc = s => String(s).replace(/</g, "<");
|
||||
let ok = await appConfirm(
|
||||
`Отправить СМС с местоположением?<br><br><b>Кому:</b> ${esc(number)}<br><br><span style="font-size:12px;color:#9aa4ae;word-break:break-all">${esc(text)}</span>`,
|
||||
"Отправить");
|
||||
if (!ok) return;
|
||||
try {
|
||||
await window.__TAURI__.core.invoke("send_sms", { number, text });
|
||||
} catch (e) { appAlert("Ошибка отправки: " + e); }
|
||||
}
|
||||
|
||||
async function renderRecordingInline(container) {
|
||||
if (!GPS.recording.active) { renderScreen("gps"); return; }
|
||||
|
||||
|
|
@ -4735,6 +4767,7 @@ async function renderRecordingInline(container) {
|
|||
</div>
|
||||
<div class="rec-bottom">
|
||||
<button id="rec-hr-toggle" class="btn-secondary rec-hr-toggle${GPS.recording.hrOn ? " active" : ""}" title="Пульсометр вкл/выкл" aria-label="ЧСС"><svg viewBox="0 0 24 22" width="22" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20S3 14.5 3 8.3C3 5.6 5 4 7.2 4 9 4 10.6 5.1 12 7 13.4 5.1 15 4 16.8 4 19 4 21 5.6 21 8.3c0 1.3-.4 2.5-1 3.7"/><path d="M2 13h5l2-3 2.4 6 2-3H22"/></svg><span id="rec-hr-num" class="rec-hr-num"></span></button>
|
||||
${localStorage.getItem("baldav_sms_number") ? '<button id="rec-sms" class="btn-secondary" title="Отправить СМС с местоположением" aria-label="СМС">СМС</button>' : ''}
|
||||
<button id="rec-pause" class="btn-secondary">${GPS.recording.manualPaused ? "Продолжить" : "Пауза"}</button>
|
||||
<button id="rec-stop" class="btn-primary gps-rec recording">Стоп</button>
|
||||
</div>
|
||||
|
|
@ -5286,6 +5319,10 @@ async function renderRecordingInline(container) {
|
|||
}
|
||||
};
|
||||
|
||||
// SMS "я тут" button — only present when a contact number is configured
|
||||
let smsBtn = container.querySelector("#rec-sms");
|
||||
if (smsBtn) smsBtn.onclick = () => sendLocationSms();
|
||||
|
||||
// Stop button
|
||||
container.querySelector("#rec-stop").onclick = async () => {
|
||||
if (GPS.recording._compassCleanup) GPS.recording._compassCleanup();
|
||||
|
|
@ -8235,6 +8272,21 @@ function showProfileModal() {
|
|||
</label>
|
||||
<div style="font-size:11px;color:#9aa4ae;margin-top:2px">Во время заезда/VR копится потребность в воде (пот), а выпитое берётся из того, что ты логируешь в баланс (жидкость по water_pct). Молчит, пока успеваешь пить; громкий сигнал (канал будильника, пробивает громкость) - только когда отстаёшь сверх порога.</div>
|
||||
<label style="font-size:13px;display:block;margin-top:4px">Сигнал при отставании, мл <input id="m-hydr-alert" type="number" inputmode="numeric" min="100" max="3000" value="${localStorage.getItem("baldav_hydr_alert") || "600"}" style="width:80px"></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
||||
<input id="m-live-on" type="checkbox" ${localStorage.getItem("baldav_live_on") === "1" ? "checked" : ""}>
|
||||
Делиться местоположением онлайн
|
||||
</label>
|
||||
<div style="font-size:11px;color:#9aa4ae;margin-top:2px">Пока включено, во время записи заезда телефон отправляет текущую точку на сервер раз в 10 секунд. Видно только тем, кому дан доступ, и только после входа. Выключается здесь же.</div>
|
||||
<label style="font-size:13px;display:block;margin-top:4px">Адрес приёма<input id="m-live-url" type="url" value="${(localStorage.getItem("baldav_live_url") || "https://4aiman.top/api/live/push").replace(/"/g, """)}" placeholder="https://4aiman.top/api/live/push" style="width:100%;margin-top:2px"></label>
|
||||
<label style="font-size:13px;display:block;margin-top:4px">Токен<input id="m-live-token" type="password" value="${(localStorage.getItem("baldav_live_token") || "").replace(/"/g, """)}" placeholder="вставь токен" style="width:100%;margin-top:2px"></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Контакт для СМС «Я тут»</label>
|
||||
<input id="m-sms-number" type="tel" inputmode="tel" value="${(localStorage.getItem("baldav_sms_number") || "").replace(/"/g, """)}" placeholder="+79990001122" style="width:100%" />
|
||||
<button type="button" id="m-sms-pick" class="btn-secondary" style="margin-top:6px">Выбрать из контактов</button>
|
||||
<div style="font-size:11px;color:#9aa4ae;margin-top:2px">Во время записи появится кнопка «СМС»: по нажатию спросит подтверждение и отправит на этот номер текст с координатами и ссылкой на Яндекс.Карты. Номер можно вписать вручную или выбрать из контактов. Работает без интернета (по сотовой сети).</div>
|
||||
</div>`,
|
||||
async overlay => {
|
||||
let weight = parseFloat(overlay.querySelector("#m-weight").value) || null;
|
||||
|
|
@ -8258,6 +8310,17 @@ function showProfileModal() {
|
|||
localStorage.setItem("baldav_hydr_alert", String(hydrAlert));
|
||||
try { await window.__TAURI__.core.invoke("set_hydration_config", { enabled: hydrOn, alertMl: hydrAlert, sweatMlPerHour: 600, reAlertMin: 15, startDeficitMl: 0 }); } catch (e) { console.error("hydration cfg", e); }
|
||||
|
||||
let liveOn = !!overlay.querySelector("#m-live-on")?.checked;
|
||||
let liveUrl = (overlay.querySelector("#m-live-url")?.value || "").trim();
|
||||
let liveToken = (overlay.querySelector("#m-live-token")?.value || "").trim();
|
||||
localStorage.setItem("baldav_live_on", liveOn ? "1" : "0");
|
||||
localStorage.setItem("baldav_live_url", liveUrl);
|
||||
localStorage.setItem("baldav_live_token", liveToken);
|
||||
try { await window.__TAURI__.core.invoke("set_live_config", { enabled: liveOn, url: liveUrl, token: liveToken }); } catch (e) { console.error("live cfg", e); }
|
||||
|
||||
let smsNumber = (overlay.querySelector("#m-sms-number")?.value || "").trim();
|
||||
localStorage.setItem("baldav_sms_number", smsNumber);
|
||||
|
||||
state.profile = { ...state.profile,
|
||||
weight_kg: weight ? weight : state.profile.weight_kg,
|
||||
height_cm: String(height), birth_year: String(year), sex: selectedSex,
|
||||
|
|
@ -8295,6 +8358,29 @@ function showProfileModal() {
|
|||
let el = overlay.querySelector(id);
|
||||
if (el) el.oninput = updateBmrDisplay;
|
||||
}
|
||||
// Pick a phone number from the system contacts (fills the SMS number field)
|
||||
let pickBtn = overlay.querySelector("#m-sms-pick");
|
||||
if (pickBtn) pickBtn.onclick = async () => {
|
||||
let field = overlay.querySelector("#m-sms-number");
|
||||
pickBtn.disabled = true;
|
||||
try {
|
||||
await window.__TAURI__.core.invoke("pick_contact");
|
||||
// The picker is an Activity; poll for its result for a few seconds.
|
||||
let got = null;
|
||||
for (let i = 0; i < 40 && !got; i++) {
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
let raw = await window.__TAURI__.core.invoke("get_contact_result");
|
||||
if (raw) got = JSON.parse(raw);
|
||||
}
|
||||
if (got && got.number) {
|
||||
field.value = got.number;
|
||||
}
|
||||
} catch (e) {
|
||||
appAlert("Не удалось выбрать контакт: " + e);
|
||||
} finally {
|
||||
pickBtn.disabled = false;
|
||||
}
|
||||
};
|
||||
}, 100);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue