diff --git a/README.md b/README.md index a7abe99..a8ef843 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,33 @@ Android-клиент для подключения к рефлекторам **S - Android 8.0 (API 26) и выше - Доступ к сети и микрофону - Рефлектор SVXLink с включённой поддержкой протокола v2 (AUTH_KEY) +- Для push-уведомлений: установить и авторизоваться в приложении **RuStore** на устройстве (оно является дистрибьютором пуша) + +## Push-уведомления (RuStore) + +Приложение интегрировано с [RuStore Push SDK](https://www.rustore.ru/help/sdk/push-notifications) (аналог Firebase Cloud Messaging). Для доставки на устройстве должен быть установлен и авторизован RuStore (или приложение-дистрибьютор экосистемы VK). + +Рассылка выполняется с сервера через API RuStore: + +``` +POST https://vkpns.rustore.ru/v1/projects/{project_id}/messages:send +Authorization: Bearer {service-token} +Content-Type: application/json + +{ + "message": { + "token": "{push-token устройства}", + "notification": { + "title": "Говорит UA1ZBE1 TG12", + "body": "..." + } + } +} +``` + +- `project_id` и `service-token` берутся в консоли RuStore: **Push-уведомления > Проекты**. +- `push-token` устройства приходит в callback `onNewToken` (лог `RuStorePush`), сохраняется в настройках приложения. +- Если в запросе есть секция `notification` — уведомление отображает сам SDK; если переданы только `data` (`title`/`body`) — приложение показывает его самостоятельно. ## Установка diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f0d79d4..d8c09cf 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -43,6 +43,7 @@ kotlin { } dependencies { + implementation("ru.rustore.sdk:pushclient:7.4.0") testImplementation("junit:junit:4.13.2") } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bf9798d..a9b6bcf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ - + @@ -9,6 +10,7 @@ + + + + + + + + + ) { + errors.forEach { Log.w(TAG, "error", it) } + } + + private fun showPush(title: String, body: String) { + val nm = getSystemService(NotificationManager::class.java) + val n = Notification.Builder(this, CHANNEL_ID) + .setContentTitle(title) + .setContentText(body) + .setSmallIcon(R.drawable.ic_launcher_fg) + .setAutoCancel(true) + .build() + nm.notify(NOTIFICATION_ID, n) + } + + companion object { + private const val TAG = "RuStorePush" + const val CHANNEL_ID = "svx_push" + private const val NOTIFICATION_ID = 2 + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ua1zbe/svxremote/SvxApp.kt b/app/src/main/java/ru/ua1zbe/svxremote/SvxApp.kt new file mode 100644 index 0000000..acaf00c --- /dev/null +++ b/app/src/main/java/ru/ua1zbe/svxremote/SvxApp.kt @@ -0,0 +1,55 @@ +package ru.ua1zbe.svxremote + +import android.app.Application +import android.app.NotificationChannel +import android.app.NotificationManager +import android.util.Log +import ru.rustore.sdk.pushclient.RuStorePushClient +import ru.ua1zbe.svxremote.prefs.Prefs + +/** + * Приложение: создаём канал пуша и инициализируем RuStore Push SDK. + * project_id берём из консоли RuStore (Push-уведомления > Проекты). + */ +class SvxApp : Application() { + + override fun onCreate() { + super.onCreate() + createPushChannel() + initRuStorePush() + } + + private fun createPushChannel() { + getSystemService(NotificationManager::class.java).createNotificationChannel( + NotificationChannel( + PushMessagingService.CHANNEL_ID, + getString(R.string.push_channel_name), + NotificationManager.IMPORTANCE_HIGH + ) + ) + } + + private fun initRuStorePush() { + try { + RuStorePushClient.init( + application = this, + projectId = RUSTORE_PROJECT_ID + ) + RuStorePushClient.getToken() + .addOnSuccessListener { token -> + Log.i(TAG, "push token: $token") + Prefs.setPushToken(this, token) + } + .addOnFailureListener { e -> + Log.w(TAG, "getToken failed", e) + } + } catch (e: Exception) { + Log.w(TAG, "init failed", e) + } + } + + companion object { + private const val TAG = "RuStorePush" + private const val RUSTORE_PROJECT_ID = "Yd7tDjVDHqsnTJwHNYN08cYIvy8fsWU-" + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/ua1zbe/svxremote/prefs/Prefs.kt b/app/src/main/java/ru/ua1zbe/svxremote/prefs/Prefs.kt index 587166b..afc315b 100644 --- a/app/src/main/java/ru/ua1zbe/svxremote/prefs/Prefs.kt +++ b/app/src/main/java/ru/ua1zbe/svxremote/prefs/Prefs.kt @@ -178,4 +178,14 @@ object Prefs { fun setRxStatsEnabled(context: Context, enabled: Boolean) { sp(context).edit().putBoolean("rx_stats_enabled", enabled).apply() } + + // ---------- RuStore Push ---------- + + /** Пуш-токен устройства RuStore (для серверной рассылки). */ + fun pushToken(context: Context): String = + sp(context).getString("push_token", "") ?: "" + + fun setPushToken(context: Context, token: String) { + sp(context).edit().putString("push_token", token).apply() + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 04fb5e6..c38f737 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -80,6 +80,8 @@ Соединение с рефлектором активно Говорит: %1$s Последний: %1$s + svx_push + Пуш-уведомления Подключиться Отключиться diff --git a/gradle.properties b/gradle.properties index 3f6ed0d..27eb325 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,4 +5,5 @@ org.gradle.daemon=false org.gradle.parallel=false org.gradle.caching=false kotlin.compiler.execution.strategy=in-process -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +android.useAndroidX=true diff --git a/settings.gradle.kts b/settings.gradle.kts index c13dd88..e324f7f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven("https://nexus-external.rustore.ru/repository/maven-rustore-exposed/") } }