Add a proper type for the vibration pattern the user can select

This commit is contained in:
cketti 2022-02-08 20:03:24 +01:00
parent c58e357030
commit 0fb6bd9198
9 changed files with 95 additions and 61 deletions

View file

@ -138,7 +138,7 @@ class AccountPreferenceSerializer(
showPictures = getEnumStringPref<ShowPictures>(storage, "$accountUuid.showPicturesEnum", ShowPictures.NEVER)
notificationSettings.isVibrateEnabled = storage.getBoolean("$accountUuid.vibrate", false)
notificationSettings.vibratePattern = storage.getInt("$accountUuid.vibratePattern", 0)
notificationSettings.vibratePattern = VibratePattern.deserialize(storage.getInt("$accountUuid.vibratePattern", 0))
notificationSettings.vibrateTimes = storage.getInt("$accountUuid.vibrateTimes", 5)
notificationSettings.isRingEnabled = storage.getBoolean("$accountUuid.ring", true)
notificationSettings.ringtone = storage.getString(
@ -323,7 +323,7 @@ class AccountPreferenceSerializer(
editor.putBoolean("$accountUuid.alwaysShowCcBcc", isAlwaysShowCcBcc)
editor.putBoolean("$accountUuid.vibrate", notificationSettings.isVibrateEnabled)
editor.putInt("$accountUuid.vibratePattern", notificationSettings.vibratePattern)
editor.putInt("$accountUuid.vibratePattern", notificationSettings.vibratePattern.serialize())
editor.putInt("$accountUuid.vibrateTimes", notificationSettings.vibrateTimes)
editor.putBoolean("$accountUuid.ring", notificationSettings.isRingEnabled)
editor.putString("$accountUuid.ringtone", notificationSettings.ringtone)
@ -606,7 +606,7 @@ class AccountPreferenceSerializer(
with(notificationSettings) {
isVibrateEnabled = false
vibratePattern = 0
vibratePattern = VibratePattern.Default
vibrateTimes = 5
isRingEnabled = true
ringtone = "content://settings/system/notification_sound"

View file

@ -26,34 +26,18 @@ class NotificationSettings {
@get:Synchronized
@set:Synchronized
var vibratePattern = 0
var vibratePattern = VibratePattern.Default
@get:Synchronized
@set:Synchronized
var vibrateTimes = 0
val vibration: LongArray
get() = getVibration(vibratePattern, vibrateTimes)
val vibrationPattern: LongArray
get() = getVibrationPattern(vibratePattern, vibrateTimes)
companion object {
// These are "off, on" patterns, specified in milliseconds
private val defaultPattern = longArrayOf(300, 200)
private val pattern1 = longArrayOf(100, 200)
private val pattern2 = longArrayOf(100, 500)
private val pattern3 = longArrayOf(200, 200)
private val pattern4 = longArrayOf(200, 500)
private val pattern5 = longArrayOf(500, 500)
fun getVibration(pattern: Int, times: Int): LongArray {
val selectedPattern = when (pattern) {
1 -> pattern1
2 -> pattern2
3 -> pattern3
4 -> pattern4
5 -> pattern5
else -> defaultPattern
}
fun getVibrationPattern(vibratePattern: VibratePattern, times: Int): LongArray {
val selectedPattern = vibratePattern.vibrationPattern
val repeatedPattern = LongArray(selectedPattern.size * times)
for (n in 0 until times) {
System.arraycopy(selectedPattern, 0, repeatedPattern, n * selectedPattern.size, selectedPattern.size)
@ -66,3 +50,38 @@ class NotificationSettings {
}
}
}
enum class VibratePattern(
/**
* These are "off, on" patterns, specified in milliseconds.
*/
val vibrationPattern: LongArray
) {
Default(vibrationPattern = longArrayOf(300, 200)),
Pattern1(vibrationPattern = longArrayOf(100, 200)),
Pattern2(vibrationPattern = longArrayOf(100, 500)),
Pattern3(vibrationPattern = longArrayOf(200, 200)),
Pattern4(vibrationPattern = longArrayOf(200, 500)),
Pattern5(vibrationPattern = longArrayOf(500, 500));
fun serialize(): Int = when (this) {
Default -> 0
Pattern1 -> 1
Pattern2 -> 2
Pattern3 -> 3
Pattern4 -> 4
Pattern5 -> 5
}
companion object {
fun deserialize(value: Int): VibratePattern = when (value) {
0 -> Default
1 -> Pattern1
2 -> Pattern2
3 -> Pattern3
4 -> Pattern4
5 -> Pattern5
else -> error("Unknown VibratePattern value: $value")
}
}
}

View file

@ -41,7 +41,7 @@ internal class BaseNotificationDataCreator {
private fun createNotificationAppearance(account: Account): NotificationAppearance {
return with(account.notificationSettings) {
val vibrationPattern = if (isVibrateEnabled) vibration else null
val vibrationPattern = if (isVibrateEnabled) vibrationPattern else null
NotificationAppearance(ringtone, vibrationPattern, ledColor)
}
}

View file

@ -194,7 +194,7 @@ class NotificationChannelManager(
private fun NotificationChannel.matches(notificationSettings: NotificationSettings): Boolean {
return lightColor == notificationSettings.ledColor &&
shouldVibrate() == notificationSettings.isVibrateEnabled &&
vibrationPattern.contentEquals(notificationSettings.vibration)
vibrationPattern.contentEquals(notificationSettings.vibrationPattern)
}
@RequiresApi(Build.VERSION_CODES.O)
@ -213,7 +213,7 @@ class NotificationChannelManager(
@RequiresApi(Build.VERSION_CODES.O)
private fun NotificationChannel.copyPropertiesFrom(notificationSettings: NotificationSettings) {
lightColor = notificationSettings.ledColor
vibrationPattern = notificationSettings.vibration
vibrationPattern = notificationSettings.vibrationPattern
enableVibration(notificationSettings.isVibrateEnabled)
}

View file

@ -5,6 +5,7 @@ import com.fsck.k9.Identity
import com.fsck.k9.K9
import com.fsck.k9.K9.LockScreenNotificationVisibility
import com.fsck.k9.NotificationSettings
import com.fsck.k9.VibratePattern
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.mockito.kotlin.mock
@ -149,13 +150,15 @@ class BaseNotificationDataCreatorTest {
@Test
fun `vibration pattern`() {
account.notificationSettings.isVibrateEnabled = true
account.notificationSettings.vibratePattern = 3
account.notificationSettings.vibratePattern = VibratePattern.Pattern3
account.notificationSettings.vibrateTimes = 2
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.appearance.vibrationPattern).isEqualTo(NotificationSettings.getVibration(3, 2))
assertThat(result.appearance.vibrationPattern).isEqualTo(
NotificationSettings.getVibrationPattern(VibratePattern.Pattern3, 2)
)
}
@Test

View file

@ -10,3 +10,8 @@ inline fun <reified T : Enum<T>> Bundle.getEnum(key: String, defaultValue: T): T
val value = getString(key) ?: return defaultValue
return enumValueOf(value)
}
inline fun <reified T : Enum<T>> Bundle.getEnum(key: String): T {
val value = getString(key) ?: error("Missing enum value for key '$key'")
return enumValueOf(value)
}

View file

@ -246,7 +246,7 @@ class AccountSettingsDataStore(
private fun getCombinedVibrationValue(): String {
return VibrationPreference.encode(
isVibrationEnabled = account.notificationSettings.isVibrateEnabled,
vibrationPattern = account.notificationSettings.vibratePattern,
vibratePattern = account.notificationSettings.vibratePattern,
vibrationTimes = account.notificationSettings.vibrateTimes
)
}

View file

@ -17,7 +17,10 @@ import androidx.appcompat.widget.SwitchCompat
import androidx.core.content.getSystemService
import androidx.preference.PreferenceDialogFragmentCompat
import com.fsck.k9.NotificationSettings
import com.fsck.k9.VibratePattern
import com.fsck.k9.ui.R
import com.fsck.k9.ui.getEnum
import com.fsck.k9.ui.putEnum
class VibrationDialogFragment : PreferenceDialogFragmentCompat() {
private val vibrator by lazy { requireContext().getSystemService<Vibrator>() ?: error("Vibrator service missing") }
@ -31,15 +34,15 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() {
val context = requireContext()
val isVibrationEnabled: Boolean
val vibrationPattern: Int
val vibratePattern: VibratePattern
val vibrationTimes: Int
if (savedInstanceState != null) {
isVibrationEnabled = savedInstanceState.getBoolean(STATE_VIBRATE)
vibrationPattern = savedInstanceState.getInt(STATE_VIBRATION_PATTERN)
vibratePattern = savedInstanceState.getEnum(STATE_VIBRATE_PATTERN)
vibrationTimes = savedInstanceState.getInt(STATE_VIBRATION_TIMES)
} else {
isVibrationEnabled = vibrationPreference.isVibrationEnabled
vibrationPattern = vibrationPreference.vibrationPattern
vibratePattern = vibrationPreference.vibratePattern
vibrationTimes = vibrationPreference.vibrationTimes
}
@ -47,7 +50,7 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() {
isVibrationEnabled,
entries = vibrationPreference.entries.map { it.toString() },
entryValues = vibrationPreference.entryValues.map { it.toString().toInt() },
vibrationPattern,
vibratePattern,
vibrationTimes
)
@ -62,7 +65,7 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() {
if (positiveResult) {
vibrationPreference.setVibration(
isVibrationEnabled = adapter.isVibrationEnabled,
vibrationPattern = adapter.vibrationPattern,
vibratePattern = adapter.vibratePattern,
vibrationTimes = adapter.vibrationTimes
)
}
@ -71,21 +74,21 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(STATE_VIBRATE, adapter.isVibrationEnabled)
outState.putInt(STATE_VIBRATION_PATTERN, adapter.vibrationPattern)
outState.putEnum(STATE_VIBRATE_PATTERN, adapter.vibratePattern)
outState.putInt(STATE_VIBRATION_TIMES, adapter.vibrationTimes)
}
private fun playVibration() {
val vibrationPattern = adapter.vibrationPattern
val vibratePattern = adapter.vibratePattern
val vibrationTimes = adapter.vibrationTimes
val combinedPattern = NotificationSettings.getVibration(vibrationPattern, vibrationTimes)
val vibrationPattern = NotificationSettings.getVibrationPattern(vibratePattern, vibrationTimes)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val vibrationEffect = VibrationEffect.createWaveform(combinedPattern, -1)
val vibrationEffect = VibrationEffect.createWaveform(vibrationPattern, -1)
vibrator.vibrate(vibrationEffect)
} else {
@Suppress("DEPRECATION")
vibrator.vibrate(combinedPattern, -1)
vibrator.vibrate(vibrationPattern, -1)
}
}
@ -93,13 +96,13 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() {
var isVibrationEnabled: Boolean,
private val entries: List<String>,
private val entryValues: List<Int>,
initialVibrationPattern: Int,
initialVibratePattern: VibratePattern,
initialVibrationTimes: Int
) : BaseAdapter() {
private var checkedEntryIndex = entryValues.indexOf(initialVibrationPattern).takeIf { it != -1 } ?: 0
private var checkedEntryIndex = entryValues.indexOf(initialVibratePattern.serialize()).takeIf { it != -1 } ?: 0
val vibrationPattern: Int
get() = entryValues[checkedEntryIndex]
val vibratePattern: VibratePattern
get() = VibratePattern.deserialize(entryValues[checkedEntryIndex])
var vibrationTimes = initialVibrationTimes
@ -209,7 +212,7 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() {
companion object {
private const val STATE_VIBRATE = "vibrate"
private const val STATE_VIBRATION_PATTERN = "vibrationPattern"
private const val STATE_VIBRATE_PATTERN = "vibratePattern"
private const val STATE_VIBRATION_TIMES = "vibrationTimes"
}
}

View file

@ -6,6 +6,7 @@ import android.util.AttributeSet
import androidx.core.content.res.TypedArrayUtils
import androidx.preference.ListPreference
import com.fsck.k9.NotificationSettings
import com.fsck.k9.VibratePattern
import com.fsck.k9.ui.R
import com.takisoft.preferencex.PreferenceFragmentCompat
@ -28,7 +29,7 @@ constructor(
internal var isVibrationEnabled: Boolean = false
private set
internal var vibrationPattern: Int = DEFAULT_VIBRATION_PATTERN
internal var vibratePattern = DEFAULT_VIBRATE_PATTERN
private set
internal var vibrationTimes: Int = DEFAULT_VIBRATION_TIMES
@ -39,7 +40,7 @@ constructor(
val (isVibrationEnabled, vibrationPattern, vibrationTimes) = decode(encoded)
this.isVibrationEnabled = isVibrationEnabled
this.vibrationPattern = vibrationPattern
this.vibratePattern = vibrationPattern
this.vibrationTimes = vibrationTimes
updateSummary()
@ -49,12 +50,12 @@ constructor(
preferenceManager.showDialog(this)
}
fun setVibration(isVibrationEnabled: Boolean, vibrationPattern: Int, vibrationTimes: Int) {
fun setVibration(isVibrationEnabled: Boolean, vibratePattern: VibratePattern, vibrationTimes: Int) {
this.isVibrationEnabled = isVibrationEnabled
this.vibrationPattern = vibrationPattern
this.vibratePattern = vibratePattern
this.vibrationTimes = vibrationTimes
val encoded = encode(isVibrationEnabled, vibrationPattern, vibrationTimes)
val encoded = encode(isVibrationEnabled, vibratePattern, vibrationTimes)
persistString(encoded)
updateSummary()
@ -62,26 +63,29 @@ constructor(
fun setVibrationFromSystem(isVibrationEnabled: Boolean, combinedPattern: List<Long>?) {
if (combinedPattern == null || combinedPattern.size < 2 || combinedPattern.size % 2 != 0) {
setVibration(isVibrationEnabled, DEFAULT_VIBRATION_PATTERN, DEFAULT_VIBRATION_TIMES)
setVibration(isVibrationEnabled, DEFAULT_VIBRATE_PATTERN, DEFAULT_VIBRATION_TIMES)
return
}
val combinedPatternArray = combinedPattern.toLongArray()
val vibrationTimes = combinedPattern.size / 2
val vibrationPattern = entryValues.asSequence()
.map { entryValue -> entryValue.toString().toInt() }
.firstOrNull { vibrationPattern ->
val testPattern = NotificationSettings.getVibration(vibrationPattern, vibrationTimes)
.map { entryValue ->
val serializedVibratePattern = entryValue.toString().toInt()
VibratePattern.deserialize(serializedVibratePattern)
}
.firstOrNull { vibratePattern ->
val testPattern = NotificationSettings.getVibrationPattern(vibratePattern, vibrationTimes)
testPattern.contentEquals(combinedPatternArray)
} ?: DEFAULT_VIBRATION_PATTERN
} ?: DEFAULT_VIBRATE_PATTERN
setVibration(isVibrationEnabled, vibrationPattern, vibrationTimes)
}
private fun updateSummary() {
summary = if (isVibrationEnabled) {
val index = entryValues.indexOf(vibrationPattern.toString())
val index = entryValues.indexOf(vibratePattern.serialize().toString())
entries[index]
} else {
context.getString(R.string.account_settings_vibrate_summary_disabled)
@ -89,7 +93,7 @@ constructor(
}
companion object {
private const val DEFAULT_VIBRATION_PATTERN = 0
private val DEFAULT_VIBRATE_PATTERN = VibratePattern.Default
private const val DEFAULT_VIBRATION_TIMES = 1
init {
@ -98,14 +102,14 @@ constructor(
)
}
fun encode(isVibrationEnabled: Boolean, vibrationPattern: Int, vibrationTimes: Int): String {
return "$isVibrationEnabled|$vibrationPattern|$vibrationTimes"
fun encode(isVibrationEnabled: Boolean, vibratePattern: VibratePattern, vibrationTimes: Int): String {
return "$isVibrationEnabled|${vibratePattern.name}|$vibrationTimes"
}
fun decode(encoded: String): Triple<Boolean, Int, Int> {
fun decode(encoded: String): Triple<Boolean, VibratePattern, Int> {
val parts = encoded.split('|')
val isVibrationEnabled = parts[0].toBoolean()
val vibrationPattern = parts[1].toInt()
val vibrationPattern = VibratePattern.valueOf(parts[1])
val vibrationTimes = parts[2].toInt()
return Triple(isVibrationEnabled, vibrationPattern, vibrationTimes)
}