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:
+79
@@ -20,8 +20,12 @@ import io.element.android.libraries.matrix.api.media.MediaPreviewValue
|
||||
import io.element.android.libraries.matrix.api.tracing.LogLevel
|
||||
import io.element.android.libraries.matrix.api.tracing.TraceLogPack
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.preferences.api.store.NotificationSound
|
||||
import io.element.android.libraries.preferences.api.store.NotificationSound.Companion.toStored
|
||||
import io.element.android.libraries.preferences.api.store.NotificationSoundChannelConfig
|
||||
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
private val developerModeKey = booleanPreferencesKey("developerMode")
|
||||
@@ -32,6 +36,12 @@ private val timelineMediaPreviewValueKey = stringPreferencesKey("timelineMediaPr
|
||||
private val liveLocationMinimumDistanceUpdateKey = intPreferencesKey("liveLocationMinimumDistanceUpdate")
|
||||
private val logLevelKey = stringPreferencesKey("logLevel")
|
||||
private val traceLogPacksKey = stringPreferencesKey("traceLogPacks")
|
||||
private val messageSoundUriKey = stringPreferencesKey("notificationMessageSoundUri")
|
||||
private val messageSoundChannelVersionKey = intPreferencesKey("notificationMessageSoundChannelVersion")
|
||||
private val messageSoundDisplayNameKey = stringPreferencesKey("notificationMessageSoundDisplayName")
|
||||
private val callRingtoneUriKey = stringPreferencesKey("notificationCallRingtoneUri")
|
||||
private val callRingtoneChannelVersionKey = intPreferencesKey("notificationCallRingtoneChannelVersion")
|
||||
private val callRingtoneDisplayNameKey = stringPreferencesKey("notificationCallRingtoneDisplayName")
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultAppPreferencesStore(
|
||||
@@ -158,6 +168,75 @@ class DefaultAppPreferencesStore(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMessageSoundFlow(): Flow<NotificationSound> {
|
||||
return store.data.map { prefs -> NotificationSound.fromStored(prefs[messageSoundUriKey]) }
|
||||
}
|
||||
|
||||
override suspend fun setMessageSoundAndIncrementVersion(sound: NotificationSound, title: String?): Int {
|
||||
var newVersion = 0
|
||||
store.edit { prefs ->
|
||||
val stored = sound.toStored()
|
||||
if (stored != null) {
|
||||
prefs[messageSoundUriKey] = stored
|
||||
} else {
|
||||
prefs.remove(messageSoundUriKey)
|
||||
}
|
||||
// Clear title on non-Custom so the picker doesn't show a stale label after a revert.
|
||||
if (sound is NotificationSound.Custom && !title.isNullOrBlank()) {
|
||||
prefs[messageSoundDisplayNameKey] = title
|
||||
} else {
|
||||
prefs.remove(messageSoundDisplayNameKey)
|
||||
}
|
||||
newVersion = (prefs[messageSoundChannelVersionKey] ?: 0) + 1
|
||||
prefs[messageSoundChannelVersionKey] = newVersion
|
||||
}
|
||||
return newVersion
|
||||
}
|
||||
|
||||
override fun getMessageSoundDisplayNameFlow(): Flow<String?> {
|
||||
return store.data.map { prefs -> prefs[messageSoundDisplayNameKey] }
|
||||
}
|
||||
|
||||
override fun getCallRingtoneFlow(): Flow<NotificationSound> {
|
||||
return store.data.map { prefs -> NotificationSound.fromStored(prefs[callRingtoneUriKey]) }
|
||||
}
|
||||
|
||||
override suspend fun setCallRingtoneAndIncrementVersion(sound: NotificationSound, title: String?): Int {
|
||||
var newVersion = 0
|
||||
store.edit { prefs ->
|
||||
val stored = sound.toStored()
|
||||
if (stored != null) {
|
||||
prefs[callRingtoneUriKey] = stored
|
||||
} else {
|
||||
prefs.remove(callRingtoneUriKey)
|
||||
}
|
||||
if (sound is NotificationSound.Custom && !title.isNullOrBlank()) {
|
||||
prefs[callRingtoneDisplayNameKey] = title
|
||||
} else {
|
||||
prefs.remove(callRingtoneDisplayNameKey)
|
||||
}
|
||||
newVersion = (prefs[callRingtoneChannelVersionKey] ?: 0) + 1
|
||||
prefs[callRingtoneChannelVersionKey] = newVersion
|
||||
}
|
||||
return newVersion
|
||||
}
|
||||
|
||||
override fun getCallRingtoneDisplayNameFlow(): Flow<String?> {
|
||||
return store.data.map { prefs -> prefs[callRingtoneDisplayNameKey] }
|
||||
}
|
||||
|
||||
override suspend fun getNotificationSoundChannelConfig(): NotificationSoundChannelConfig {
|
||||
val prefs = store.data.first()
|
||||
return NotificationSoundChannelConfig(
|
||||
messageSound = NotificationSound.fromStored(prefs[messageSoundUriKey]),
|
||||
messageSoundVersion = prefs[messageSoundChannelVersionKey] ?: 0,
|
||||
messageSoundDisplayName = prefs[messageSoundDisplayNameKey],
|
||||
callRingtone = NotificationSound.fromStored(prefs[callRingtoneUriKey]),
|
||||
callRingtoneVersion = prefs[callRingtoneChannelVersionKey] ?: 0,
|
||||
callRingtoneDisplayName = prefs[callRingtoneDisplayNameKey],
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun reset() {
|
||||
store.edit { it.clear() }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user