Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1c9b8d15e | ||
|
|
4bcb04a655 | ||
|
|
82d249e697 | ||
|
|
bc12959147 | ||
|
|
28330dd044 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,4 +12,5 @@ captures/
|
|||||||
*.aab
|
*.aab
|
||||||
keystore.properties
|
keystore.properties
|
||||||
*.jks
|
*.jks
|
||||||
tools/.push.secrets
|
tools/.push.secrets
|
||||||
|
tools/push_all.sh
|
||||||
147
README.md
147
README.md
@@ -1,27 +1,148 @@
|
|||||||
# APRS RadioRubka
|
# APRS RadioRubka
|
||||||
|
|
||||||
Android-приложение на Kotlin: анимированная заставка «APRS» и карта APRS в WebView.
|
Android-приложение: анимированная заставка «APRS» → веб-карта APRS и
|
||||||
|
пуш-уведомления (RuStore Push). Кнопка «назад» закрывает приложение, экран не
|
||||||
|
гаснет при активном приложении.
|
||||||
|
|
||||||
## Функции
|
- Пакет: `ru.ua1zbe.aprsmap`
|
||||||
|
- Карта: https://aprs.radiorubka.org/?center=67.5096,32.8271&zoom=8
|
||||||
|
(задаётся строкой `map_url` в `app/src/main/res/values/strings.xml`)
|
||||||
|
- Репозиторий: https://git2.ua1zbe.ru/ua1zbe/aprsmap
|
||||||
|
|
||||||
- Анимированная заставка при старте (градиент, волна букв APRS, прогресс-бар).
|
## Стек сборки
|
||||||
- Карта `https://aprs.radiorubka.org` открывается внутри приложения (WebView, JS, zoom).
|
|
||||||
- Кнопка «Назад» закрывает приложение.
|
- Gradle wrapper 9.2.0, AGP 8.13.2, Kotlin 2.2.21, JDK 25
|
||||||
|
- compileSdk 34, targetSdk 34, minSdk 26
|
||||||
|
|
||||||
|
> Не использовать системный gradle 4.4.1 — только `./gradlew`.
|
||||||
|
|
||||||
## Сборка
|
## Сборка
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./gradlew assembleDebug
|
./gradlew :app:assembleDebug # отладочный APK
|
||||||
|
./gradlew :app:assembleRelease # релизный APK (подписан)
|
||||||
```
|
```
|
||||||
|
|
||||||
APK: `app/build/outputs/apk/debug/app-debug.apk`
|
Релизный APK: `app/build/outputs/apk/release/app-release.apk`.
|
||||||
|
|
||||||
## Требования
|
Перед релизной сборкой нужны (в git НЕ хранятся):
|
||||||
|
|
||||||
- Android 8.0 (API 26) и выше
|
- `keystore.properties` — подпись релиза (файл в корне проекта):
|
||||||
- Разрешения: только INTERNET
|
```
|
||||||
|
storeFile=/полный/путь/aprsmap-release.jks
|
||||||
|
storePassword=...
|
||||||
|
keyAlias=aprsmap
|
||||||
|
keyPassword=...
|
||||||
|
```
|
||||||
|
- `aprsmap-release.jks` — файл ключа (`.gitignore`).
|
||||||
|
|
||||||
## Ссылки
|
Отпечаток подписи (SHA-256) релиза. Получение:
|
||||||
|
|
||||||
- Gitea: http://git2.ua1zbe.ru/ua1zbe/aprsmap
|
```bash
|
||||||
- Карта: https://aprs.radiorubka.org
|
keytool -list -v -keystore aprsmap-release.jks -alias aprsmap -storepass <пароль> | grep SHA256
|
||||||
|
# или
|
||||||
|
$ANDROID_HOME/build-tools/35.0.0/apksigner verify --print-certs app-release.apk
|
||||||
|
```
|
||||||
|
|
||||||
|
## Версионирование
|
||||||
|
|
||||||
|
`app/build.gradle.kts`:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
versionCode = 4 // инкремент для каждого релиза
|
||||||
|
versionName = "1.0.3"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Установка на телефон (adb)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ANDROID_HOME/platform-tools/adb install -r app/build/outputs/apk/release/app-release.apk
|
||||||
|
$ANDROID_HOME/platform-tools/adb shell am start -n ru.ua1zbe.aprsmap/.SplashActivity
|
||||||
|
```
|
||||||
|
|
||||||
|
Запуск только через `.SplashActivity` (MainActivity не exported).
|
||||||
|
Google Play Protect может блокировать sideload — выключите усиленную защиту.
|
||||||
|
|
||||||
|
## Выпуск релиза на Gitea
|
||||||
|
|
||||||
|
1. Собрать `assembleRelease`.
|
||||||
|
2. Токен API Gitea — в `/tmp/gitea_token.txt` (`Authorization: token`).
|
||||||
|
3. Создать релиз и загрузить APK (см. пример ниже). Имена тегов: `v1.0.0`, `v1.0.1`, ...
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TOKEN=$(cat /tmp/gitea_token.txt)
|
||||||
|
curl -s -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||||
|
-d '{"tag_name":"v1.0.3","target_commitish":"main","name":"v1.0.3","body":"...","draft":false,"prerelease":false}' \
|
||||||
|
"https://git2.ua1zbe.ru/api/v1/repos/ua1zbe/aprsmap/releases"
|
||||||
|
# затем прикрепить APK, взяв id релиза:
|
||||||
|
curl -s -H "Authorization: token $TOKEN" \
|
||||||
|
-F "attachment=@app/build/outputs/apk/release/app-release.apk" \
|
||||||
|
"https://git2.ua1zbe.ru/api/v1/repos/ua1zbe/aprsmap/releases/<id>/assets?name=APRS_RadioRubka_v1.0.3.apk"
|
||||||
|
```
|
||||||
|
|
||||||
|
Сверка обновлений идёт по интерфейсу
|
||||||
|
`https://git2.ua1zbe.ru/api/v1/repos/ua1zbe/aprsmap/releases/latest`
|
||||||
|
— поэтому репозиторий публичный.
|
||||||
|
|
||||||
|
## Автообновление
|
||||||
|
|
||||||
|
- При старте `MainActivity.checkForUpdates()` сравнивает `versionName` с последним
|
||||||
|
релизом Gitea; если есть новее — диалог «Обновить».
|
||||||
|
- APK скачивается в `cacheDir/updates/`, установка через
|
||||||
|
`ContentProvider` `UpdateFileProvider` (authority `ru.ua1zbe.aprsmap.fileprovider`)
|
||||||
|
+ `ACTION_VIEW`, при необходимости запрос «Разрешить установку из этого источника».
|
||||||
|
- `tools/push.sh` — служебное, рассылка пушей (см. ниже).
|
||||||
|
|
||||||
|
## Пуш-уведомления (RuStore Push)
|
||||||
|
|
||||||
|
Актуально с v1.0.3. SDK `ru.rustore.sdk:pushclient:7.4.0`, топик рассылки —
|
||||||
|
**`aprsmap`**.
|
||||||
|
|
||||||
|
- Параметры в `app/src/main/java/ru/ua1zbe/aprsmap/App.kt`:
|
||||||
|
`RUSTORE_PROJECT_ID` (project id из консоли RuStore),
|
||||||
|
`PUSH_TOPIC`. При старте: `RuStorePushClient.init` → `getToken` →
|
||||||
|
`subscribeToTopic("aprsmap")`.
|
||||||
|
- `PushMessagingService` получает `onNewToken`/`onMessageReceived`; уведомления с
|
||||||
|
секцией `notification` показывает сам SDK, `data` с ключами `title`/`body` —
|
||||||
|
приложение. Канал `aprs_push`.
|
||||||
|
- Токен устройства пишется в `shared_prefs/aprsmap.xml` (`push_token`) и виден в
|
||||||
|
logcat `RuStorePush:`.
|
||||||
|
|
||||||
|
### Секреты (в git НЕ хранить)
|
||||||
|
|
||||||
|
`tools/.push.secrets` (в `.gitignore`):
|
||||||
|
|
||||||
|
```
|
||||||
|
SERVICE_TOKEN="..."
|
||||||
|
PROJECT_ID="..."
|
||||||
|
```
|
||||||
|
|
||||||
|
SDK на устройстве требует установленный и авторизованный RuStore.
|
||||||
|
|
||||||
|
### Рассылка всем подписчикам топика
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./tools/push.sh "Заголовок" "Текст"
|
||||||
|
```
|
||||||
|
|
||||||
|
Реализация: `POST https://vkpns-topics.rustore.ru/v1/projects/{PROJECT_ID}/topics/aprsmap/publish`
|
||||||
|
с `Authorization: Bearer {SERVICE_TOKEN}`. Ответ
|
||||||
|
`{"message":"payload has been successfully published to topic aprsmap"}` — успех.
|
||||||
|
|
||||||
|
### Точечная отправка на устройство
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST \
|
||||||
|
"https://vkpns.rustore.ru/v1/projects/${PROJECT_ID}/messages:send" \
|
||||||
|
-H "Authorization: Bearer ${SERVICE_TOKEN}" -H "Content-Type: application/json" \
|
||||||
|
-d '{"message":{"token":"<push_token устройства>","notification":{"title":"...","body":"..."}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка на устройстве
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb logcat -d | grep -E "RuStorePush:"
|
||||||
|
adb shell dumpsys notification | grep -E "aprs_push|aprsmap"
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидаемое: `push token: ...`, `subscribed to aprsmap`, `push displayed by SDK`.
|
||||||
@@ -18,8 +18,8 @@ android {
|
|||||||
applicationId = "ru.ua1zbe.aprsmap"
|
applicationId = "ru.ua1zbe.aprsmap"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 4
|
versionCode = 5
|
||||||
versionName = "1.0.3"
|
versionName = "1.0.4"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@@ -74,6 +74,15 @@ class MainActivity : Activity() {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPageFinished(view: WebView, url: String?) {
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
view.evaluateJavascript(
|
||||||
|
"document.head.insertAdjacentHTML('beforeend','<style id=\"aprs-fix\">' + " +
|
||||||
|
org.json.JSONObject.quote(FIX_CSS) + " + '</style>')",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
webChromeClient = object : WebChromeClient() {
|
webChromeClient = object : WebChromeClient() {
|
||||||
@@ -254,5 +263,35 @@ class MainActivity : Activity() {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "APRSmap"
|
private const val TAG = "APRSmap"
|
||||||
private const val REQ_NOTIFICATIONS = 100
|
private const val REQ_NOTIFICATIONS = 100
|
||||||
|
|
||||||
|
private val FIX_CSS = """
|
||||||
|
.leaflet-popup-content-wrapper {
|
||||||
|
max-width: 88vw !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.leaflet-infowindow-content .leaflet-popup-content-wrapper {
|
||||||
|
width: min(88vw, 440px) !important;
|
||||||
|
}
|
||||||
|
.leaflet-infowindow-content .leaflet-popup-content {
|
||||||
|
width: 100% !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content p {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content img {
|
||||||
|
max-width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
.modal-content-body, .td-modal-content, .modal-inner-content {
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
.overview-content-summary, .overview-content-station-list {
|
||||||
|
max-width: 100% !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,37 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Рассылка пуша всем подписчикам топика "aprsmap" (RuStore Push).
|
# Рассылка пуш-уведомления ВСЕМ подписчикам топика "aprsmap" (RuStore Push).
|
||||||
# Использование: ./push.sh "Заголовок" "Текст"
|
# Топик = все устройства, где установлено приложение APRS RadioRubka
|
||||||
# project_id и service token читаются из tools/.push.secrets (в git не хранится).
|
# и оно хотя бы раз запускалось (подписка на клиенте выполняется сама).
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# ./push.sh "Заголовок" "Текст"
|
||||||
|
#
|
||||||
|
# Пример для крон/автоматики:
|
||||||
|
# */5 * * * * /home/ua1zbe/aprsmap/tools/push.sh "Новость" "$(cat /tmp/news.txt)"
|
||||||
|
#
|
||||||
|
# Project id и service token читаются из tools/.push.secrets (в git не хранится)
|
||||||
|
# либо из переменных окружения PUSH_SECRETS / SERVICE_TOKEN / PROJECT_ID.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
[ -f "$DIR/.push.secrets" ] && . "$DIR/.push.secrets"
|
SECRETS="${PUSH_SECRETS:-$DIR/.push.secrets}"
|
||||||
|
[ -f "$SECRETS" ] && . "$SECRETS"
|
||||||
|
|
||||||
SERVICE_TOKEN="${SERVICE_TOKEN:-${SSP:-}}"
|
SERVICE_TOKEN="${SERVICE_TOKEN:-${SSP:-}}"
|
||||||
PROJECT_ID="${PROJECT_ID:-}"
|
PROJECT_ID="${PROJECT_ID:-}"
|
||||||
|
|
||||||
[ -n "$SERVICE_TOKEN" ] || { echo "SERVICE_TOKEN is not set" >&2; exit 1; }
|
[ -n "$SERVICE_TOKEN" ] || { echo "Ошибка: SERVICE_TOKEN не задан" >&2; exit 1; }
|
||||||
[ -n "$PROJECT_ID" ] || { echo "PROJECT_ID is not set" >&2; exit 1; }
|
[ -n "$PROJECT_ID" ] || { echo "Ошибка: PROJECT_ID не задан" >&2; exit 1; }
|
||||||
|
|
||||||
title="${1:?usage: push.sh <title> <body>}"
|
title="${1:?Использование: $0 \"Заголовок\" \"Текст\"}"
|
||||||
body="${2:-}"
|
body="${2:-}"
|
||||||
|
|
||||||
curl -s -X POST \
|
resp=$(curl -s -X POST \
|
||||||
"https://vkpns-topics.rustore.ru/v1/projects/${PROJECT_ID}/topics/aprsmap/publish" \
|
"https://vkpns-topics.rustore.ru/v1/projects/${PROJECT_ID}/topics/aprsmap/publish" \
|
||||||
-H "Authorization: Bearer ${SERVICE_TOKEN}" \
|
-H "Authorization: Bearer ${SERVICE_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{\"message\":{\"notification\":{\"title\":\"${title}\",\"body\":\"${body}\"}}}"
|
-d "{\"message\":{\"notification\":{\"title\":\"${title}\",\"body\":\"${body}\"}}}")
|
||||||
echo
|
|
||||||
|
echo "$resp" | grep -q "successfully published" && {
|
||||||
|
echo "OK: пуш отправлен всем подписчикам топика aprsmap"
|
||||||
|
} || { echo "Ошибка отправки: $resp" >&2; exit 1; }
|
||||||
Reference in New Issue
Block a user