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:
4aiman 2026-10-26 16:12:11 +03:00
commit 1410062d85
8 changed files with 415 additions and 1 deletions

View file

@ -0,0 +1,72 @@
# 2026-10-26 - Трансляция местоположения (botovod) + СМС «я тут»
Плана в `docs/plans/` под эти две фичи не было - делали по устной постановке пользователя
в этой сессии.
## Feature 1: live-локация через botovod (этап 1)
Постановка (подтверждена): точка со хвостом, отображение ТОЛЬКО после входа, доступ у админа
и выбранных. Отправка раз в 10 секунд через интернет (пользователь поменял с минуты:
«гадать что случилось за 10 секунд легче чем за минуту»). Переиспользуем SRP-авторизацию botovod.
### Серверная часть (botovod, /home/chaiman/dev/vps/botovod -> на VPS /home/chaiman/botovod)
- `POST /api/live/push` - телефон шлёт `{token,lat,lng,spd,ts}`, авторизация общим токеном (без
сессии, это фоновый сервис). Пишет `db/live.json` (last + хвост, максимум 240 точек ~40 мин).
- `GET /api/live/pos` - отдаёт last+trail+stale, только залогиненному зрителю из списка доступа
(`get_session_user` сверяется с `db/live_config.json.viewers`). Иначе 401.
- `GET /live` - страница карты (`web/live.html`, Leaflet + публичные тайлы OSM, поллинг pos раз
в 5с, точка + хвост, бар статуса в эфире/нет свежих). Без входа - 302 на `/login`.
- Конфиг: `db/live_config.json` `{token, viewers:["4aiman","key55"]}`; состояние `db/live.json`.
Оба в `.gitignore` botovod - токен в репозиторий не попадает. Код закоммичен (botovod
`271d1a6`), секреты - нет.
- Заведён пользователь `key55` (SRP, пароль задал пользователь; отдаст жене и сменит).
### Деплой и проверка на стенде (4aiman.top)
- Серверная app.js == локальная база (md5 совпал) до правок - затёрли ровно её + свои роуты.
Бэкап: `app.js.bak-pre-live-*` на VPS. Залиты app.js, web/live.html, db/live*.json,
db/users/key55.json (только эти, не вся db/), владелец chaiman, `node -c` на VPS OK, рестарт
botovod.service - active.
- Проверено curl на живом 4aiman.top: неверный токен 401, верный push 200 `{ok:true}`
(точка записалась в live.json), pos без сессии 401, `/live` без сессии 302 -> /login, кривые
координаты 400. Тестовую точку из live.json убрал.
### Часть в приложении (baldav)
- Rust `set_live_config(enabled,url,token)` -> `live_share.json` в корне приложения
(`write_app_root_file`). url/token только на устройстве.
- Kotlin `LiveShare.kt` (синглтон, как Hydration): читает конфиг, раз в 10с из flush-петли
`GpsRecorderService` шлёт POST в фоновом потоке (HttpURLConnection, таймауты 5с), диагностика
в `live_share_log.jsonl`. Проводка: `lastSpeed`, `LiveShare.tick(...)` в петле, `reset()` на
старте сессии.
- UI: галка + поля адреса/токена в профиле; дефолт адреса `https://4aiman.top/api/live/push`,
выключено по умолчанию. Синк конфига на старте приложения (как гидратация).
## Feature 2: СМС «я тут»
Постановка: интент с предзаполненным текстом, ссылка на **Яндекс.Карты** (не гугл); контакт -
и выбор из системных, и ручной ввод; кнопка во время трекинга -> подтверждение -> отправка.
Работает на 2G/EDGE без интернета.
- Решение по отправке: если дано SEND_SMS - шлём напрямую `SmsManager` (одно подтверждение в
приложении); иначе запрашиваем разрешение на будущее и открываем системный SMS-композер с
предзаполненным текстом. Так работает в любом случае, ОС сама даёт выбор. (Это осознанный
компромисс, не заглушка.)
- Rust: `send_sms(number,text)` (пишет `sms_request.json` + команду `send_sms` в мост),
`pick_contact()` (команда `pick_contact`), `get_contact_result()` (читает+чистит
`contact_result.json`).
- Kotlin (MainActivity): `pickContactLauncher` (ACTION_PICK по Phone.CONTENT_URI, номер читается
из URI без READ_CONTACTS), команды `pick_contact`/`send_sms` в `processGpsCommand`, хелпер
`sendSms` (прямая отправка или фолбэк на композер). Разрешение `SEND_SMS` в манифесте.
- UI: контакт в профиле (ручной ввод + «Выбрать из контактов» с поллингом результата пикера);
текст `Я тут: <lat>,<lon> https://yandex.ru/maps/?pt=<lon>,<lat>&z=16&l=map`; кнопка «СМС» в
панели записи (видна, только если номер настроен), подтверждение через `appConfirm`.
## Статус проверки
- Серверная часть Feature 1 - проверена на стенде (4aiman.top), работает.
- Приложение (обе фичи) - собрано чисто, **build 0.1.524**, Rust+Kotlin компилируются. На
устройстве НЕ проверено: телефон не был подключён на момент сборки. Поставить и проверить:
(1) пуш координат и карту `/live` под key55; (2) выбор контакта и отправку СМС.
## TODO (следующий этап live-локации, записано со слов пользователя)
- Отправлять на VPS все сохранённые маршруты из меню baldav + слать id маршрута вместе с
координатами; опционально показывать сохранённые «проезженные дороги» как в baldav; key55
должен видеть то же, что на экране трекинга; шарить статические данные из baldav по интернету.