RuStore Push SDK: получение и отображение пуш-уведомлений
- Подключён ru.rustore.sdk:pushclient:7.4.0 (репозиторий Rustore). - SvxApp: канал svx_push и инициализация RuStorePushClient с project_id; push-токен устройства сохраняется в настройках. - PushMessagingService (RuStoreMessagingService): onNewToken, onMessageReceived (data-only пуши показываем сами), onDeletedMessages, onError. - Манифест: сервис, значок/канал по умолчанию, android.useAndroidX=true (зависимость тянет androidx). - README: как рассылать пуши через vkpns.rustore.ru API.
This commit is contained in:
27
README.md
27
README.md
@@ -43,6 +43,33 @@ Android-клиент для подключения к рефлекторам **S
|
|||||||
- Android 8.0 (API 26) и выше
|
- Android 8.0 (API 26) и выше
|
||||||
- Доступ к сети и микрофону
|
- Доступ к сети и микрофону
|
||||||
- Рефлектор SVXLink с включённой поддержкой протокола v2 (AUTH_KEY)
|
- Рефлектор 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`) — приложение показывает его самостоятельно.
|
||||||
|
|
||||||
## Установка
|
## Установка
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation("ru.rustore.sdk:pushclient:7.4.0")
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".SvxApp"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
@@ -20,6 +22,22 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="mediaPlayback" />
|
android:foregroundServiceType="mediaPlayback" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".PushMessagingService"
|
||||||
|
android:exported="true"
|
||||||
|
tools:ignore="ExportedService">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="ru.rustore.sdk.pushclient.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="ru.rustore.sdk.pushclient.default_notification_icon"
|
||||||
|
android:resource="@drawable/ic_launcher_fg" />
|
||||||
|
<meta-data
|
||||||
|
android:name="ru.rustore.sdk.pushclient.default_notification_channel_id"
|
||||||
|
android:value="@string/push_channel_id" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".SettingsActivity"
|
android:name=".SettingsActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package ru.ua1zbe.svxremote
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.util.Log
|
||||||
|
import ru.rustore.sdk.pushclient.messaging.exception.RuStorePushClientException
|
||||||
|
import ru.rustore.sdk.pushclient.messaging.model.RemoteMessage
|
||||||
|
import ru.rustore.sdk.pushclient.messaging.service.RuStoreMessagingService
|
||||||
|
import ru.ua1zbe.svxremote.prefs.Prefs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Приём пуш-уведомлений RuStore.
|
||||||
|
*
|
||||||
|
* Если сервер прислал секцию `notification` — SDK сам отображает уведомление.
|
||||||
|
* Если присланы только `data` (title/body) — отображаем уведомление сами.
|
||||||
|
*/
|
||||||
|
class PushMessagingService : RuStoreMessagingService() {
|
||||||
|
|
||||||
|
override fun onNewToken(token: String) {
|
||||||
|
Log.i(TAG, "onNewToken: $token")
|
||||||
|
Prefs.setPushToken(this, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessageReceived(message: RemoteMessage) {
|
||||||
|
if (message.notification != null) {
|
||||||
|
Log.i(TAG, "push displayed by SDK: ${message.data}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val title = message.data["title"] ?: getString(R.string.app_name)
|
||||||
|
val body = message.data["body"] ?: ""
|
||||||
|
if (title.isNotEmpty() || body.isNotEmpty()) {
|
||||||
|
showPush(title, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeletedMessages() {
|
||||||
|
Log.i(TAG, "onDeletedMessages")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(errors: List<RuStorePushClientException>) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
55
app/src/main/java/ru/ua1zbe/svxremote/SvxApp.kt
Normal file
55
app/src/main/java/ru/ua1zbe/svxremote/SvxApp.kt
Normal file
@@ -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-"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -178,4 +178,14 @@ object Prefs {
|
|||||||
fun setRxStatsEnabled(context: Context, enabled: Boolean) {
|
fun setRxStatsEnabled(context: Context, enabled: Boolean) {
|
||||||
sp(context).edit().putBoolean("rx_stats_enabled", enabled).apply()
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,8 @@
|
|||||||
<string name="notif_connected">Соединение с рефлектором активно</string>
|
<string name="notif_connected">Соединение с рефлектором активно</string>
|
||||||
<string name="notif_talking">Говорит: %1$s</string>
|
<string name="notif_talking">Говорит: %1$s</string>
|
||||||
<string name="notif_last_talker">Последний: %1$s</string>
|
<string name="notif_last_talker">Последний: %1$s</string>
|
||||||
|
<string name="push_channel_id">svx_push</string>
|
||||||
|
<string name="push_channel_name">Пуш-уведомления</string>
|
||||||
|
|
||||||
<string name="btn_connect">Подключиться</string>
|
<string name="btn_connect">Подключиться</string>
|
||||||
<string name="btn_disconnect">Отключиться</string>
|
<string name="btn_disconnect">Отключиться</string>
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ org.gradle.parallel=false
|
|||||||
org.gradle.caching=false
|
org.gradle.caching=false
|
||||||
kotlin.compiler.execution.strategy=in-process
|
kotlin.compiler.execution.strategy=in-process
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
|
android.useAndroidX=true
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven("https://nexus-external.rustore.ru/repository/maven-rustore-exposed/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user