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:
Benoit Marty
2026-05-28 16:54:12 +02:00
committed by GitHub
parent bdedf41f99
commit c88edc9621
79 changed files with 3135 additions and 139 deletions
+1
View File
@@ -19,6 +19,7 @@ dependencies {
api(projects.libraries.pushproviders.api)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.preferences.api)
implementation(projects.libraries.matrixui)
implementation(projects.libraries.workmanager.api)
implementation(projects.tests.testutils)
@@ -0,0 +1,31 @@
/*
* 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.push.test.notifications
import io.element.android.libraries.preferences.api.store.NotificationSound
import io.element.android.libraries.push.api.notifications.NotificationSoundUpdater
import io.element.android.tests.testutils.lambda.lambdaError
class FakeNotificationSoundUpdater(
private val recreateNoisyChannelLambda: (sound: NotificationSound, version: Int) -> Unit = { _, _ -> lambdaError() },
private val recreateRingingCallChannelLambda: (sound: NotificationSound, version: Int) -> Unit = { _, _ -> lambdaError() },
private val readNoisyChannelSoundLambda: () -> NotificationSound? = { null },
private val readRingingCallChannelSoundLambda: () -> NotificationSound? = { null },
) : NotificationSoundUpdater {
override fun recreateNoisyChannel(sound: NotificationSound, version: Int) {
recreateNoisyChannelLambda(sound, version)
}
override fun recreateRingingCallChannel(sound: NotificationSound, version: Int) {
recreateRingingCallChannelLambda(sound, version)
}
override suspend fun readNoisyChannelSound(): NotificationSound? = readNoisyChannelSoundLambda()
override suspend fun readRingingCallChannelSound(): NotificationSound? = readRingingCallChannelSoundLambda()
}
@@ -0,0 +1,19 @@
/*
* 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.push.test.notifications
import io.element.android.libraries.push.api.notifications.SoundDisplayNameResolver
class FakeSoundDisplayNameResolver(
// Default resolves to a non-null sentinel so a default-constructed fake doesn't masquerade
// as "every Custom URI is unresolvable" — that would trip mid-session detection paths in
// every test that uses default fakes. Tests that need the unavailable case pass `{ null }`.
private val resolveLambda: suspend (uri: String) -> String? = { "FakeRingtoneTitle" },
) : SoundDisplayNameResolver {
override suspend fun resolveCustomSoundTitle(uri: String): String? = resolveLambda(uri)
}
@@ -0,0 +1,24 @@
/*
* 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.push.test.notifications.sound
import io.element.android.libraries.push.api.notifications.sound.NotificationSoundCopier
import io.element.android.libraries.push.api.notifications.sound.NotificationSoundCopier.CopyResult
import io.element.android.libraries.push.api.notifications.sound.NotificationSoundCopier.SoundSlot
import io.element.android.tests.testutils.lambda.lambdaError
class FakeNotificationSoundCopier(
private val copyLambda: (String, SoundSlot) -> CopyResult = { _, _ -> lambdaError() },
private val deleteStoredSoundForLambda: (SoundSlot) -> Unit = {},
) : NotificationSoundCopier {
override suspend fun copyToAppFiles(sourceUriString: String, slot: SoundSlot): CopyResult = copyLambda(sourceUriString, slot)
override suspend fun deleteStoredSoundFor(slot: SoundSlot) {
deleteStoredSoundForLambda(slot)
}
}