feat: настройка звука push-уведомлений (0.5.4)
Единый звук для каналов, выбор системный/свой в Устройстве, исправлен краш ringtone picker.
This commit is contained in:
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId = "ru.kalinamall.seaca"
|
applicationId = "ru.kalinamall.seaca"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 16
|
versionCode = 18
|
||||||
versionName = "0.5.2"
|
versionName = "0.5.4"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package ru.kalinamall.seaca
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import ru.kalinamall.seaca.push.NotificationHelper
|
import ru.kalinamall.seaca.push.NotificationHelper
|
||||||
|
|
||||||
|
import ru.kalinamall.seaca.push.NotificationSoundResolver
|
||||||
|
|
||||||
class SeacaApplication : Application() {
|
class SeacaApplication : Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
NotificationHelper.ensureChannels(this)
|
NotificationHelper.ensureChannels(this, NotificationSoundResolver.defaultNotificationUri())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package ru.kalinamall.seaca.data
|
||||||
|
|
||||||
|
enum class NotificationSoundSource(
|
||||||
|
val storageKey: String,
|
||||||
|
val label: String,
|
||||||
|
) {
|
||||||
|
SYSTEM("system", "Системный звук"),
|
||||||
|
CUSTOM("custom", "Свой звук"),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromStorage(value: String?): NotificationSoundSource =
|
||||||
|
entries.firstOrNull { it.storageKey == value } ?: SYSTEM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class NotificationSoundSettings(
|
||||||
|
val source: NotificationSoundSource,
|
||||||
|
val customUri: String?,
|
||||||
|
)
|
||||||
@@ -40,12 +40,27 @@ class SessionStore(private val context: Context) {
|
|||||||
DateTimeDisplayMode.fromStorage(prefs[KEY_DATE_TIME_FORMAT])
|
DateTimeDisplayMode.fromStorage(prefs[KEY_DATE_TIME_FORMAT])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val notificationSoundSettings: Flow<NotificationSoundSettings> = context.dataStore.data.map { prefs ->
|
||||||
|
NotificationSoundSettings(
|
||||||
|
source = NotificationSoundSource.fromStorage(prefs[KEY_NOTIFICATION_SOUND_SOURCE]),
|
||||||
|
customUri = prefs[KEY_NOTIFICATION_SOUND_URI],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getNotificationMode(): NotificationMode =
|
suspend fun getNotificationMode(): NotificationMode =
|
||||||
NotificationMode.fromStorage(context.dataStore.data.first()[KEY_NOTIFICATION_MODE])
|
NotificationMode.fromStorage(context.dataStore.data.first()[KEY_NOTIFICATION_MODE])
|
||||||
|
|
||||||
suspend fun getDateTimeDisplayMode(): DateTimeDisplayMode =
|
suspend fun getDateTimeDisplayMode(): DateTimeDisplayMode =
|
||||||
DateTimeDisplayMode.fromStorage(context.dataStore.data.first()[KEY_DATE_TIME_FORMAT])
|
DateTimeDisplayMode.fromStorage(context.dataStore.data.first()[KEY_DATE_TIME_FORMAT])
|
||||||
|
|
||||||
|
suspend fun getNotificationSoundSettings(): NotificationSoundSettings {
|
||||||
|
val prefs = context.dataStore.data.first()
|
||||||
|
return NotificationSoundSettings(
|
||||||
|
source = NotificationSoundSource.fromStorage(prefs[KEY_NOTIFICATION_SOUND_SOURCE]),
|
||||||
|
customUri = prefs[KEY_NOTIFICATION_SOUND_URI],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun setNotificationMode(mode: NotificationMode) {
|
suspend fun setNotificationMode(mode: NotificationMode) {
|
||||||
context.dataStore.edit { it[KEY_NOTIFICATION_MODE] = mode.storageKey }
|
context.dataStore.edit { it[KEY_NOTIFICATION_MODE] = mode.storageKey }
|
||||||
}
|
}
|
||||||
@@ -54,6 +69,17 @@ class SessionStore(private val context: Context) {
|
|||||||
context.dataStore.edit { it[KEY_DATE_TIME_FORMAT] = mode.storageKey }
|
context.dataStore.edit { it[KEY_DATE_TIME_FORMAT] = mode.storageKey }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun setNotificationSound(source: NotificationSoundSource, customUri: String?) {
|
||||||
|
context.dataStore.edit {
|
||||||
|
it[KEY_NOTIFICATION_SOUND_SOURCE] = source.storageKey
|
||||||
|
if (customUri.isNullOrBlank()) {
|
||||||
|
it.remove(KEY_NOTIFICATION_SOUND_URI)
|
||||||
|
} else {
|
||||||
|
it[KEY_NOTIFICATION_SOUND_URI] = customUri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getBaseUrl(): String? = context.dataStore.data.first()[KEY_BASE_URL]
|
suspend fun getBaseUrl(): String? = context.dataStore.data.first()[KEY_BASE_URL]
|
||||||
|
|
||||||
suspend fun trustAllCerts(): Boolean = context.dataStore.data.first()[KEY_TRUST_ALL] ?: false
|
suspend fun trustAllCerts(): Boolean = context.dataStore.data.first()[KEY_TRUST_ALL] ?: false
|
||||||
@@ -114,6 +140,8 @@ class SessionStore(private val context: Context) {
|
|||||||
private val KEY_TRUST_ALL = booleanPreferencesKey("trust_all_certs")
|
private val KEY_TRUST_ALL = booleanPreferencesKey("trust_all_certs")
|
||||||
private val KEY_NOTIFICATION_MODE = stringPreferencesKey("notification_mode")
|
private val KEY_NOTIFICATION_MODE = stringPreferencesKey("notification_mode")
|
||||||
private val KEY_DATE_TIME_FORMAT = stringPreferencesKey("date_time_format")
|
private val KEY_DATE_TIME_FORMAT = stringPreferencesKey("date_time_format")
|
||||||
|
private val KEY_NOTIFICATION_SOUND_SOURCE = stringPreferencesKey("notification_sound_source")
|
||||||
|
private val KEY_NOTIFICATION_SOUND_URI = stringPreferencesKey("notification_sound_uri")
|
||||||
private const val KEY_ACCESS = "access_token"
|
private const val KEY_ACCESS = "access_token"
|
||||||
private const val KEY_REFRESH = "refresh_token"
|
private const val KEY_REFRESH = "refresh_token"
|
||||||
private const val KEY_DEVICE_ID = "device_id"
|
private const val KEY_DEVICE_ID = "device_id"
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import android.app.NotificationManager
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.media.AudioAttributes
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import ru.kalinamall.seaca.MainActivity
|
import ru.kalinamall.seaca.MainActivity
|
||||||
@@ -18,35 +20,47 @@ object NotificationHelper {
|
|||||||
const val CHANNEL_HIGH_SILENT = "sac_high_silent"
|
const val CHANNEL_HIGH_SILENT = "sac_high_silent"
|
||||||
const val CHANNEL_CRITICAL_SILENT = "sac_critical_silent"
|
const val CHANNEL_CRITICAL_SILENT = "sac_critical_silent"
|
||||||
|
|
||||||
fun ensureChannels(context: Context) {
|
private val soundChannelIds = listOf(CHANNEL_DEFAULT, CHANNEL_HIGH, CHANNEL_CRITICAL)
|
||||||
|
private var appliedSoundKey: String? = null
|
||||||
|
|
||||||
|
fun ensureChannels(context: Context, soundUri: Uri = NotificationSoundResolver.defaultNotificationUri()) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||||
|
val soundKey = soundUri.toString()
|
||||||
val mgr = context.getSystemService(NotificationManager::class.java)
|
val mgr = context.getSystemService(NotificationManager::class.java)
|
||||||
|
if (soundKey != appliedSoundKey) {
|
||||||
|
soundChannelIds.forEach { mgr.deleteNotificationChannel(it) }
|
||||||
|
appliedSoundKey = soundKey
|
||||||
|
}
|
||||||
|
val audioAttributes = AudioAttributes.Builder()
|
||||||
|
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
|
||||||
|
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||||
|
.build()
|
||||||
mgr.createNotificationChannel(
|
mgr.createNotificationChannel(
|
||||||
NotificationChannel(
|
soundChannel(context, CHANNEL_DEFAULT, R.string.channel_default, NotificationManager.IMPORTANCE_DEFAULT, soundUri, audioAttributes),
|
||||||
CHANNEL_DEFAULT,
|
|
||||||
context.getString(R.string.channel_default),
|
|
||||||
NotificationManager.IMPORTANCE_DEFAULT,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
mgr.createNotificationChannel(
|
mgr.createNotificationChannel(
|
||||||
NotificationChannel(
|
soundChannel(context, CHANNEL_HIGH, R.string.channel_high, NotificationManager.IMPORTANCE_HIGH, soundUri, audioAttributes),
|
||||||
CHANNEL_HIGH,
|
|
||||||
context.getString(R.string.channel_high),
|
|
||||||
NotificationManager.IMPORTANCE_HIGH,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
mgr.createNotificationChannel(
|
mgr.createNotificationChannel(
|
||||||
NotificationChannel(
|
soundChannel(context, CHANNEL_CRITICAL, R.string.channel_critical, NotificationManager.IMPORTANCE_HIGH, soundUri, audioAttributes),
|
||||||
CHANNEL_CRITICAL,
|
|
||||||
context.getString(R.string.channel_critical),
|
|
||||||
NotificationManager.IMPORTANCE_HIGH,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
mgr.createNotificationChannel(silentChannel(context, CHANNEL_DEFAULT_SILENT, R.string.channel_default_silent))
|
mgr.createNotificationChannel(silentChannel(context, CHANNEL_DEFAULT_SILENT, R.string.channel_default_silent))
|
||||||
mgr.createNotificationChannel(silentChannel(context, CHANNEL_HIGH_SILENT, R.string.channel_high_silent))
|
mgr.createNotificationChannel(silentChannel(context, CHANNEL_HIGH_SILENT, R.string.channel_high_silent))
|
||||||
mgr.createNotificationChannel(silentChannel(context, CHANNEL_CRITICAL_SILENT, R.string.channel_critical_silent))
|
mgr.createNotificationChannel(silentChannel(context, CHANNEL_CRITICAL_SILENT, R.string.channel_critical_silent))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun soundChannel(
|
||||||
|
context: Context,
|
||||||
|
id: String,
|
||||||
|
nameRes: Int,
|
||||||
|
importance: Int,
|
||||||
|
soundUri: Uri,
|
||||||
|
audioAttributes: AudioAttributes,
|
||||||
|
): NotificationChannel = NotificationChannel(id, context.getString(nameRes), importance).apply {
|
||||||
|
setSound(soundUri, audioAttributes)
|
||||||
|
enableVibration(true)
|
||||||
|
}
|
||||||
|
|
||||||
private fun silentChannel(context: Context, id: String, nameRes: Int): NotificationChannel =
|
private fun silentChannel(context: Context, id: String, nameRes: Int): NotificationChannel =
|
||||||
NotificationChannel(id, context.getString(nameRes), NotificationManager.IMPORTANCE_DEFAULT).apply {
|
NotificationChannel(id, context.getString(nameRes), NotificationManager.IMPORTANCE_DEFAULT).apply {
|
||||||
setSound(null, null)
|
setSound(null, null)
|
||||||
@@ -66,8 +80,9 @@ object NotificationHelper {
|
|||||||
kind: String?,
|
kind: String?,
|
||||||
id: String?,
|
id: String?,
|
||||||
silent: Boolean = false,
|
silent: Boolean = false,
|
||||||
|
soundUri: Uri = NotificationSoundResolver.defaultNotificationUri(),
|
||||||
) {
|
) {
|
||||||
ensureChannels(context)
|
ensureChannels(context, soundUri)
|
||||||
val channel = resolveChannel(severity, silent)
|
val channel = resolveChannel(severity, silent)
|
||||||
val intent = Intent(context, MainActivity::class.java).apply {
|
val intent = Intent(context, MainActivity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package ru.kalinamall.seaca.push
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.media.RingtoneManager
|
||||||
|
import android.net.Uri
|
||||||
|
import ru.kalinamall.seaca.data.NotificationSoundSettings
|
||||||
|
import ru.kalinamall.seaca.data.NotificationSoundSource
|
||||||
|
|
||||||
|
object NotificationSoundResolver {
|
||||||
|
fun resolve(context: Context, settings: NotificationSoundSettings): Uri {
|
||||||
|
return when (settings.source) {
|
||||||
|
NotificationSoundSource.SYSTEM -> defaultNotificationUri()
|
||||||
|
NotificationSoundSource.CUSTOM -> {
|
||||||
|
settings.customUri?.let { Uri.parse(it) } ?: defaultNotificationUri()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defaultNotificationUri(): Uri =
|
||||||
|
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
|
||||||
|
fun displayName(context: Context, settings: NotificationSoundSettings): String {
|
||||||
|
val uri = resolve(context, settings)
|
||||||
|
return RingtoneManager.getRingtone(context, uri)?.getTitle(context) ?: "—"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,12 @@ class SeacaMessagingService : FirebaseMessagingService() {
|
|||||||
val data = message.data
|
val data = message.data
|
||||||
val title = data["title"] ?: message.notification?.title ?: "SAC"
|
val title = data["title"] ?: message.notification?.title ?: "SAC"
|
||||||
val body = data["body"] ?: message.notification?.body ?: ""
|
val body = data["body"] ?: message.notification?.body ?: ""
|
||||||
|
val soundUri = if (silent) {
|
||||||
|
NotificationSoundResolver.defaultNotificationUri()
|
||||||
|
} else {
|
||||||
|
val settings = runBlocking { repo.session.getNotificationSoundSettings() }
|
||||||
|
NotificationSoundResolver.resolve(this, settings)
|
||||||
|
}
|
||||||
NotificationHelper.show(
|
NotificationHelper.show(
|
||||||
context = this,
|
context = this,
|
||||||
title = title,
|
title = title,
|
||||||
@@ -35,6 +41,7 @@ class SeacaMessagingService : FirebaseMessagingService() {
|
|||||||
kind = data["kind"],
|
kind = data["kind"],
|
||||||
id = data["id"],
|
id = data["id"],
|
||||||
silent = silent,
|
silent = silent,
|
||||||
|
soundUri = soundUri,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
package ru.kalinamall.seaca.ui.screens
|
package ru.kalinamall.seaca.ui.screens
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Intent
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import android.media.RingtoneManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -35,6 +46,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -46,8 +58,12 @@ import ru.kalinamall.seaca.data.HostDetail
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import ru.kalinamall.seaca.data.DateTimeDisplayMode
|
import ru.kalinamall.seaca.data.DateTimeDisplayMode
|
||||||
import ru.kalinamall.seaca.data.NotificationMode
|
import ru.kalinamall.seaca.data.NotificationMode
|
||||||
|
import ru.kalinamall.seaca.data.NotificationSoundSettings
|
||||||
|
import ru.kalinamall.seaca.data.NotificationSoundSource
|
||||||
import ru.kalinamall.seaca.data.ProblemDetail
|
import ru.kalinamall.seaca.data.ProblemDetail
|
||||||
import ru.kalinamall.seaca.data.SacRepository
|
import ru.kalinamall.seaca.data.SacRepository
|
||||||
|
import ru.kalinamall.seaca.push.NotificationHelper
|
||||||
|
import ru.kalinamall.seaca.push.NotificationSoundResolver
|
||||||
import ru.kalinamall.seaca.ui.components.BackArrowButton
|
import ru.kalinamall.seaca.ui.components.BackArrowButton
|
||||||
import ru.kalinamall.seaca.ui.components.ForwardArrowButton
|
import ru.kalinamall.seaca.ui.components.ForwardArrowButton
|
||||||
import ru.kalinamall.seaca.ui.util.SacDateTimes
|
import ru.kalinamall.seaca.ui.util.SacDateTimes
|
||||||
@@ -418,11 +434,69 @@ fun DeviceScreen(repo: SacRepository, onLogout: () -> Unit) {
|
|||||||
var username by remember { mutableStateOf<String?>(null) }
|
var username by remember { mutableStateOf<String?>(null) }
|
||||||
var role by remember { mutableStateOf<String?>(null) }
|
var role by remember { mutableStateOf<String?>(null) }
|
||||||
var deviceId by remember { mutableStateOf(0) }
|
var deviceId by remember { mutableStateOf(0) }
|
||||||
|
val context = LocalContext.current
|
||||||
val notificationMode by repo.session.notificationMode.collectAsState(initial = NotificationMode.PUSH_SOUND)
|
val notificationMode by repo.session.notificationMode.collectAsState(initial = NotificationMode.PUSH_SOUND)
|
||||||
|
val soundSettings by repo.session.notificationSoundSettings.collectAsStateWithLifecycle(
|
||||||
|
initialValue = NotificationSoundSettings(NotificationSoundSource.SYSTEM, null),
|
||||||
|
)
|
||||||
val dateTimeMode by repo.session.dateTimeDisplayMode.collectAsStateWithLifecycle(
|
val dateTimeMode by repo.session.dateTimeDisplayMode.collectAsStateWithLifecycle(
|
||||||
initialValue = DateTimeDisplayMode.SLASH,
|
initialValue = DateTimeDisplayMode.SLASH,
|
||||||
)
|
)
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val activity = context as? FragmentActivity
|
||||||
|
val latestSoundSettings by rememberUpdatedState(soundSettings)
|
||||||
|
|
||||||
|
suspend fun applySoundChannels(settings: NotificationSoundSettings) {
|
||||||
|
NotificationHelper.ensureChannels(
|
||||||
|
context,
|
||||||
|
NotificationSoundResolver.resolve(context, settings),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(soundSettings) {
|
||||||
|
if (notificationMode == NotificationMode.PUSH_SOUND) {
|
||||||
|
applySoundChannels(soundSettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val ringtonePickerLauncher = rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult(),
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode != Activity.RESULT_OK) return@rememberLauncherForActivityResult
|
||||||
|
val uri = readRingtonePickerResult(result.data)
|
||||||
|
scope.launch {
|
||||||
|
if (uri == null) {
|
||||||
|
repo.session.setNotificationSound(NotificationSoundSource.SYSTEM, null)
|
||||||
|
} else {
|
||||||
|
repo.session.setNotificationSound(NotificationSoundSource.CUSTOM, uri.toString())
|
||||||
|
}
|
||||||
|
applySoundChannels(repo.session.getNotificationSoundSettings())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openRingtonePicker() {
|
||||||
|
if (activity == null) {
|
||||||
|
Toast.makeText(context, "Не удалось открыть выбор звука", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val currentUri = NotificationSoundResolver.resolve(context, latestSoundSettings)
|
||||||
|
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
|
||||||
|
putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
|
||||||
|
putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false)
|
||||||
|
putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, NotificationSoundResolver.defaultNotificationUri())
|
||||||
|
putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentUri)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ringtonePickerLauncher.launch(intent)
|
||||||
|
} catch (_: ActivityNotFoundException) {
|
||||||
|
Toast.makeText(context, "На устройстве нет выбора звука уведомлений", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun previewSound() {
|
||||||
|
RingtoneManager.getRingtone(context, NotificationSoundResolver.resolve(context, soundSettings))?.play()
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
baseUrl = repo.session.getBaseUrl()
|
baseUrl = repo.session.getBaseUrl()
|
||||||
@@ -482,6 +556,71 @@ fun DeviceScreen(repo: SacRepository, onLogout: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (notificationMode == NotificationMode.PUSH_SOUND) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.notification_sound_title),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
)
|
||||||
|
NotificationSoundSource.entries.forEach { source ->
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 2.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = soundSettings.source == source,
|
||||||
|
onClick = {
|
||||||
|
when (source) {
|
||||||
|
NotificationSoundSource.SYSTEM -> {
|
||||||
|
scope.launch {
|
||||||
|
repo.session.setNotificationSound(source, null)
|
||||||
|
applySoundChannels(repo.session.getNotificationSoundSettings())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NotificationSoundSource.CUSTOM -> {
|
||||||
|
if (soundSettings.customUri.isNullOrBlank()) {
|
||||||
|
openRingtonePicker()
|
||||||
|
} else {
|
||||||
|
scope.launch {
|
||||||
|
repo.session.setNotificationSound(source, soundSettings.customUri)
|
||||||
|
applySoundChannels(repo.session.getNotificationSoundSettings())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
source.label,
|
||||||
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
R.string.notification_sound_current,
|
||||||
|
NotificationSoundResolver.displayName(context, soundSettings),
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
if (soundSettings.source == NotificationSoundSource.CUSTOM) {
|
||||||
|
Button(onClick = { openRingtonePicker() }) {
|
||||||
|
Text(stringResource(R.string.notification_sound_pick))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button(onClick = { previewSound() }) {
|
||||||
|
Text(stringResource(R.string.notification_sound_preview))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.date_time_format_title),
|
stringResource(R.string.date_time_format_title),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
@@ -591,4 +730,14 @@ private fun Field(label: String, value: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun readRingtonePickerResult(data: Intent?): Uri? {
|
||||||
|
if (data == null) return null
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun JsonElement.toPrettyString(): String = toString()
|
private fun JsonElement.toPrettyString(): String = toString()
|
||||||
|
|||||||
@@ -9,4 +9,8 @@
|
|||||||
<string name="channel_critical_silent">SAC: критические (без звука)</string>
|
<string name="channel_critical_silent">SAC: критические (без звука)</string>
|
||||||
<string name="notification_mode_title">Push-уведомления</string>
|
<string name="notification_mode_title">Push-уведомления</string>
|
||||||
<string name="date_time_format_title">Формат даты и времени</string>
|
<string name="date_time_format_title">Формат даты и времени</string>
|
||||||
|
<string name="notification_sound_title">Звук уведомлений</string>
|
||||||
|
<string name="notification_sound_pick">Выбрать звук</string>
|
||||||
|
<string name="notification_sound_preview">Проверить звук</string>
|
||||||
|
<string name="notification_sound_current">Сейчас: %1$s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user