mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 21:32:34 +00:00
Notification settings: pick custom sounds for messages and call ringtones (#6897)
* Notification settings: pick custom sounds for messages and call ringtones (#6702) * Feature: add notification sound settings for messages and calls * Add sound not available banner, accessibility checks * Fix build and Konsist convention violations - libraries:preferences:api now uses io.element.android-compose-library so NotificationSound's @Immutable annotation can resolve, matching the pattern in libraries:matrix:api. - Rename SoundUnavailableBannerStateProvider to NotificationSoundUnavailableStateProvider so the class name contains the parameter type as required by KonsistClassNameTest. * Suppress LargeClass on RoomListPresenterTest The three new sound-unavailable tests pushed the class past Detekt's default LargeClass threshold. Matches the suppression pattern already used on five presenter test classes under features/messages/impl/test. * NotificationSettingsView: guard ringtone-picker launchers in inspection mode rememberLauncherForActivityResult requires a LocalActivityResultRegistryOwner, which Paparazzi-driven preview snapshot tests don't provide, causing NotificationSettingsViewPreview to throw IllegalStateException for every preview parameter. Extract the click handlers into a helper that returns a no-op when LocalInspectionMode.current is true; the runtime path is unchanged. * Handle unresolved notification sounds mid session * Quality checks, refactor mid session error ui * Suppress detekt LargeClass on NotificationSettingsPresenterTest * Remove notification sound resilience layer, replace with file copy * Reselect element sound option, code cleanup * Add element fade notification sound option * Fix new file headers * Revert to flowOf(Unit) for announcement service * Add ringtone picker dialog to match message selector * Update strings in conjunction with ios * Update test to reflect new string * Sync strings. * Update screenshots * Fix test * Remove not properly located translations. This will be fixed in the next String sync. * Fix test on CI --------- Co-authored-by: Jenna Vassar <jenna.vassar@toptal.com> Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
@@ -9,7 +9,7 @@ import extension.testCommonDependencies
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
+22
@@ -41,5 +41,27 @@ interface AppPreferencesStore {
|
||||
suspend fun setTracingLogPacks(targets: Set<TraceLogPack>)
|
||||
fun getTracingLogPacksFlow(): Flow<Set<TraceLogPack>>
|
||||
|
||||
fun getMessageSoundFlow(): Flow<NotificationSound>
|
||||
|
||||
/**
|
||||
* Atomically persists [sound] (with copy-time [title] for Custom; cleared otherwise) and
|
||||
* bumps the channel version. Single transaction so process death can't desync URI and version.
|
||||
*/
|
||||
suspend fun setMessageSoundAndIncrementVersion(sound: NotificationSound, title: String?): Int
|
||||
|
||||
/** Title captured at copy time. Null for SystemDefault / Silent or pre-title persisted data. */
|
||||
fun getMessageSoundDisplayNameFlow(): Flow<String?>
|
||||
|
||||
fun getCallRingtoneFlow(): Flow<NotificationSound>
|
||||
|
||||
/** See [setMessageSoundAndIncrementVersion]. */
|
||||
suspend fun setCallRingtoneAndIncrementVersion(sound: NotificationSound, title: String?): Int
|
||||
|
||||
/** See [getMessageSoundDisplayNameFlow]. */
|
||||
fun getCallRingtoneDisplayNameFlow(): Flow<String?>
|
||||
|
||||
/** Single-snapshot read of all sound prefs; used at boot to seed channels without N reads. */
|
||||
suspend fun getNotificationSoundChannelConfig(): NotificationSoundChannelConfig
|
||||
|
||||
suspend fun reset()
|
||||
}
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.preferences.api.store
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
/**
|
||||
* The user's choice of notification sound for a notification channel.
|
||||
*/
|
||||
@Immutable
|
||||
sealed interface NotificationSound {
|
||||
/** Use the system's default tone for this channel (e.g. notification or ringtone). */
|
||||
data object SystemDefault : NotificationSound
|
||||
|
||||
/**
|
||||
* Use the bundled in-app sound. Semantically meaningful only for the noisy message channel,
|
||||
* where it resolves to `R.raw.message`; the ringing-call channel has no bundled tone and
|
||||
* treats this the same as [SystemDefault].
|
||||
*/
|
||||
data object ElementDefault : NotificationSound
|
||||
|
||||
/**
|
||||
* Use the bundled in-app fade sound. Like [ElementDefault] this is meaningful only for the
|
||||
* noisy message channel (`R.raw.element_fade`); the ringing-call channel has no bundled tone
|
||||
* and treats this the same as [SystemDefault].
|
||||
*/
|
||||
data object ElementFade : NotificationSound
|
||||
|
||||
/** Produce no sound. */
|
||||
data object Silent : NotificationSound
|
||||
|
||||
/** Use the ringtone at [uri]. */
|
||||
data class Custom(val uri: String) : NotificationSound
|
||||
|
||||
companion object {
|
||||
// String? round-trip used by [AppPreferencesStore]:
|
||||
// null -> SystemDefault, "silent" -> Silent, "element_default" -> ElementDefault,
|
||||
// "element_fade" -> ElementFade, else -> Custom(uri).
|
||||
private const val STORED_SILENT = "silent"
|
||||
private const val STORED_ELEMENT_DEFAULT = "element_default"
|
||||
private const val STORED_ELEMENT_FADE = "element_fade"
|
||||
|
||||
fun fromStored(value: String?): NotificationSound = when (value) {
|
||||
null -> SystemDefault
|
||||
STORED_SILENT -> Silent
|
||||
STORED_ELEMENT_DEFAULT -> ElementDefault
|
||||
STORED_ELEMENT_FADE -> ElementFade
|
||||
else -> Custom(value)
|
||||
}
|
||||
|
||||
fun NotificationSound.toStored(): String? = when (this) {
|
||||
SystemDefault -> null
|
||||
Silent -> STORED_SILENT
|
||||
ElementDefault -> STORED_ELEMENT_DEFAULT
|
||||
ElementFade -> STORED_ELEMENT_FADE
|
||||
is Custom -> uri
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshot of the persisted notification-sound state, returned by
|
||||
* [AppPreferencesStore.getNotificationSoundChannelConfig] in a single read.
|
||||
*/
|
||||
data class NotificationSoundChannelConfig(
|
||||
val messageSound: NotificationSound,
|
||||
val messageSoundVersion: Int,
|
||||
val messageSoundDisplayName: String?,
|
||||
val callRingtone: NotificationSound,
|
||||
val callRingtoneVersion: Int,
|
||||
val callRingtoneDisplayName: String?,
|
||||
)
|
||||
Reference in New Issue
Block a user