mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 06:55:41 +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:
@@ -20,5 +20,6 @@ dependencies {
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixmedia.api)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.pushproviders.api)
|
||||
}
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.api.notifications
|
||||
|
||||
import io.element.android.libraries.preferences.api.store.NotificationSound
|
||||
|
||||
/**
|
||||
* Reads and writes the sound on the message / ringing-call channels. Writes create a new
|
||||
* versioned channel because Android forbids mutating sound after creation.
|
||||
*/
|
||||
interface NotificationSoundUpdater {
|
||||
fun recreateNoisyChannel(sound: NotificationSound, version: Int)
|
||||
|
||||
fun recreateRingingCallChannel(sound: NotificationSound, version: Int)
|
||||
|
||||
/** Current channel sound classified into [NotificationSound]. Null when the channel doesn't exist. */
|
||||
suspend fun readNoisyChannelSound(): NotificationSound?
|
||||
|
||||
/** See [readNoisyChannelSound]. */
|
||||
suspend fun readRingingCallChannelSound(): NotificationSound?
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.api.notifications
|
||||
|
||||
/**
|
||||
* Resolves the user-facing title of a custom notification sound URI (the title shown by the
|
||||
* system ringtone picker). Lifts the [android.media.RingtoneManager] side effect out of UI code
|
||||
* so views and presenters stay testable.
|
||||
*/
|
||||
interface SoundDisplayNameResolver {
|
||||
/** Returns the title of the ringtone at [uri], or null if it cannot be resolved. */
|
||||
suspend fun resolveCustomSoundTitle(uri: String): String?
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.api.notifications.sound
|
||||
|
||||
/**
|
||||
* Copies a picked notification sound into app-private storage and re-exposes it via the app's
|
||||
* FileProvider, so the persisted reference outlives the original source.
|
||||
*/
|
||||
interface NotificationSoundCopier {
|
||||
suspend fun copyToAppFiles(sourceUriString: String, slot: SoundSlot): CopyResult
|
||||
|
||||
/**
|
||||
* Removes any previously copied file for [slot] from app-private storage. No-op when no file
|
||||
* exists. Call this when persisting a non-[NotificationSound.Custom] pick (SystemDefault /
|
||||
* Silent) so the previous Custom copy doesn't linger as orphaned bytes — [copyToAppFiles]
|
||||
* already sweeps stale files inline for Custom-to-Custom transitions.
|
||||
*/
|
||||
suspend fun deleteStoredSoundFor(slot: SoundSlot)
|
||||
|
||||
enum class SoundSlot { Message, Call }
|
||||
|
||||
sealed interface CopyResult {
|
||||
/**
|
||||
* @property fileProviderUriString FileProvider URI to persist as the channel sound.
|
||||
* @property displayName [android.media.Ringtone.getTitle] for the source, or null. The
|
||||
* picker substitutes a localised fallback label, so localisation stays in the UI layer.
|
||||
*/
|
||||
data class Success(val fileProviderUriString: String, val displayName: String?) : CopyResult
|
||||
|
||||
/** [android.media.RingtoneManager] could not open the source URI as a Ringtone. */
|
||||
data object UnplayableSource : CopyResult
|
||||
|
||||
/** The bytes copied successfully, but the resulting file is not a playable Ringtone. */
|
||||
data object UnplayableCopy : CopyResult
|
||||
|
||||
/** Source exceeded the 5 MB cap. */
|
||||
data object FileTooLarge : CopyResult
|
||||
|
||||
/** Any other I/O or unexpected failure. */
|
||||
data class Failure(val cause: Throwable) : CopyResult
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user