Unify settings to configure notification vibration

This commit is contained in:
cketti 2022-02-01 19:46:01 +01:00
parent 5c0b99fbdc
commit 64c6bcd48f
8 changed files with 179 additions and 77 deletions

View file

@ -212,9 +212,8 @@ class NotificationChannelManager(
@RequiresApi(Build.VERSION_CODES.O)
private fun NotificationChannel.copyPropertiesFrom(notificationSetting: NotificationSetting) {
lightColor = notificationSetting.ledColor
if (shouldVibrate()) {
vibrationPattern = notificationSetting.vibration
}
vibrationPattern = notificationSetting.vibration
enableVibration(notificationSetting.isVibrateEnabled)
}
private val Account.messagesNotificationChannelSuffix: String

View file

@ -30,7 +30,6 @@ class AccountSettingsDataStore(
"account_notify" -> account.isNotifyNewMail
"account_notify_self" -> account.isNotifySelfNewMail
"account_notify_contacts_mail_only" -> account.isNotifyContactsMailOnly
"account_vibrate" -> account.notificationSetting.isVibrateEnabled
"account_led" -> account.notificationSetting.isLedEnabled
"account_notify_sync" -> account.isNotifySync
"openpgp_hide_sign_only" -> account.isOpenPgpHideSignOnly
@ -56,7 +55,6 @@ class AccountSettingsDataStore(
"account_notify" -> account.isNotifyNewMail = value
"account_notify_self" -> account.isNotifySelfNewMail = value
"account_notify_contacts_mail_only" -> account.isNotifyContactsMailOnly = value
"account_vibrate" -> account.notificationSetting.isVibrateEnabled = value
"account_led" -> account.notificationSetting.setLed(value)
"account_notify_sync" -> account.isNotifySync = value
"openpgp_hide_sign_only" -> account.isOpenPgpHideSignOnly = value
@ -134,7 +132,7 @@ class AccountSettingsDataStore(
"spam_folder" -> loadSpecialFolder(account.spamFolderId, account.spamFolderSelection)
"trash_folder" -> loadSpecialFolder(account.trashFolderId, account.trashFolderSelection)
"folder_notify_new_mail_mode" -> account.folderNotifyNewMailMode.name
"account_combined_vibration_pattern" -> getCombinedVibrationPattern()
"account_combined_vibration" -> getCombinedVibrationValue()
"account_remote_search_num_results" -> account.remoteSearchNumResults.toString()
"account_ringtone" -> account.notificationSetting.ringtone
else -> defValue
@ -178,7 +176,7 @@ class AccountSettingsDataStore(
"spam_folder" -> saveSpecialFolderSelection(value, account::setSpamFolderId)
"trash_folder" -> saveSpecialFolderSelection(value, account::setTrashFolderId)
"folder_notify_new_mail_mode" -> account.folderNotifyNewMailMode = Account.FolderMode.valueOf(value)
"account_combined_vibration_pattern" -> setCombinedVibrationPattern(value)
"account_combined_vibration" -> setCombinedVibrationValue(value)
"account_remote_search_num_results" -> account.remoteSearchNumResults = value.toInt()
"account_ringtone" -> with(account.notificationSetting) {
isRingEnabled = true
@ -245,15 +243,17 @@ class AccountSettingsDataStore(
return prefix + (specialFolderId?.toString() ?: FolderListPreference.NO_FOLDER_VALUE)
}
private fun getCombinedVibrationPattern(): String {
return VibrationPatternPreference.encode(
private fun getCombinedVibrationValue(): String {
return VibrationPreference.encode(
isVibrationEnabled = account.notificationSetting.isVibrateEnabled,
vibrationPattern = account.notificationSetting.vibratePattern,
vibrationTimes = account.notificationSetting.vibrateTimes
)
}
private fun setCombinedVibrationPattern(value: String) {
val (vibrationPattern, vibrationTimes) = VibrationPatternPreference.decode(value)
private fun setCombinedVibrationValue(value: String) {
val (isVibrationEnabled, vibrationPattern, vibrationTimes) = VibrationPreference.decode(value)
account.notificationSetting.isVibrateEnabled = isVibrationEnabled
account.notificationSetting.vibratePattern = vibrationPattern
account.notificationSetting.vibrateTimes = vibrationTimes
notificationSettingsChanged = true

View file

@ -54,7 +54,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
private val vibrator by lazy { requireContext().getSystemService<Vibrator>() }
private lateinit var dataStore: AccountSettingsDataStore
private var notificationLightColorPreference: ColorPickerPreference? = null
private var notificationVibrationPatternPreference: VibrationPatternPreference? = null
private var notificationVibrationPreference: VibrationPreference? = null
private val accountUuid: String by lazy {
checkNotNull(arguments?.getString(ARG_ACCOUNT_UUID)) { "$ARG_ACCOUNT_UUID == null" }
@ -190,8 +190,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
private fun initializeNotifications(account: Account) {
if (vibrator?.hasVibrator() != true) {
findPreference<Preference>(PREFERENCE_NOTIFICATION_VIBRATION_PATTERN)?.remove()
findPreference<Preference>(PREFERENCE_NOTIFICATION_ENABLE_VIBRATION)?.remove()
findPreference<Preference>(PREFERENCE_NOTIFICATION_VIBRATION)?.remove()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -201,9 +200,8 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
preference.isEnabled = false
}
findPreference<VibrationPatternPreference>(PREFERENCE_NOTIFICATION_VIBRATION_PATTERN)?.let { preference ->
notificationVibrationPatternPreference = preference
preference.dependency = null
findPreference<VibrationPreference>(PREFERENCE_NOTIFICATION_VIBRATION)?.let { preference ->
notificationVibrationPreference = preference
preference.isEnabled = false
}
@ -226,7 +224,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
}
private fun maybeUpdateNotificationPreferences(account: Account) {
if (notificationLightColorPreference != null || notificationVibrationPatternPreference != null) {
if (notificationLightColorPreference != null || notificationVibrationPreference != null) {
updateNotificationPreferences(account)
}
}
@ -243,12 +241,12 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
}
}
notificationVibrationPatternPreference?.let { preference ->
val vibrationEnabled = notificationConfiguration.isVibrationEnabled
preference.isEnabled = vibrationEnabled
if (vibrationEnabled) {
preference.setVibrationPatternFromSystem(notificationConfiguration.vibrationPattern)
}
notificationVibrationPreference?.let { preference ->
preference.setVibrationFromSystem(
isVibrationEnabled = notificationConfiguration.isVibrationEnabled,
combinedPattern = notificationConfiguration.vibrationPattern
)
preference.isEnabled = true
}
}
@ -439,8 +437,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
private const val PREFERENCE_SPAM_FOLDER = "spam_folder"
private const val PREFERENCE_TRASH_FOLDER = "trash_folder"
private const val PREFERENCE_NOTIFICATION_LIGHT_COLOR = "led_color"
private const val PREFERENCE_NOTIFICATION_ENABLE_VIBRATION = "account_vibrate"
private const val PREFERENCE_NOTIFICATION_VIBRATION_PATTERN = "account_combined_vibration_pattern"
private const val PREFERENCE_NOTIFICATION_VIBRATION = "account_combined_vibration"
private const val PREFERENCE_NOTIFICATION_CHANNELS = "notification_channels"
private const val PREFERENCE_NOTIFICATION_SETTINGS_MESSAGES = "open_notification_settings_messages"
private const val PREFERENCE_NOTIFICATION_SETTINGS_MISCELLANEOUS = "open_notification_settings_miscellaneous"
@ -448,7 +445,6 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
private val PRE_SDK26_NOTIFICATION_PREFERENCES = arrayOf(
"account_ringtone",
PREFERENCE_NOTIFICATION_ENABLE_VIBRATION,
"account_led",
)

View file

@ -13,41 +13,45 @@ import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SwitchCompat
import androidx.core.content.getSystemService
import androidx.preference.PreferenceDialogFragmentCompat
import com.fsck.k9.NotificationSetting
import com.fsck.k9.ui.R
class VibrationPatternDialogFragment : PreferenceDialogFragmentCompat() {
class VibrationDialogFragment : PreferenceDialogFragmentCompat() {
private val vibrator by lazy { requireContext().getSystemService<Vibrator>() ?: error("Vibrator service missing") }
private val vibratePatternPreference: VibrationPatternPreference
get() = preference as VibrationPatternPreference
private val vibrationPreference: VibrationPreference
get() = preference as VibrationPreference
private lateinit var adapter: VibrationPatternAdapter
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext()
val isVibrationEnabled: Boolean
val vibrationPattern: Int
val vibrationTimes: Int
if (savedInstanceState != null) {
isVibrationEnabled = savedInstanceState.getBoolean(STATE_VIBRATE)
vibrationPattern = savedInstanceState.getInt(STATE_VIBRATION_PATTERN)
vibrationTimes = savedInstanceState.getInt(STATE_VIBRATION_TIMES)
} else {
vibrationPattern = vibratePatternPreference.vibrationPattern
vibrationTimes = vibratePatternPreference.vibrationTimes
isVibrationEnabled = vibrationPreference.isVibrationEnabled
vibrationPattern = vibrationPreference.vibrationPattern
vibrationTimes = vibrationPreference.vibrationTimes
}
adapter = VibrationPatternAdapter(
entries = vibratePatternPreference.entries.map { it.toString() },
entryValues = vibratePatternPreference.entryValues.map { it.toString().toInt() },
isVibrationEnabled,
entries = vibrationPreference.entries.map { it.toString() },
entryValues = vibrationPreference.entryValues.map { it.toString().toInt() },
vibrationPattern,
vibrationTimes
)
return AlertDialog.Builder(context)
.setTitle(preference.title)
.setAdapter(adapter, null)
.setPositiveButton(R.string.okay_action, ::onClick)
.setNegativeButton(R.string.cancel_action, ::onClick)
@ -56,12 +60,17 @@ class VibrationPatternDialogFragment : PreferenceDialogFragmentCompat() {
override fun onDialogClosed(positiveResult: Boolean) {
if (positiveResult) {
vibratePatternPreference.setVibrationPattern(adapter.vibrationPattern, adapter.vibrationTimes)
vibrationPreference.setVibration(
isVibrationEnabled = adapter.isVibrationEnabled,
vibrationPattern = adapter.vibrationPattern,
vibrationTimes = adapter.vibrationTimes
)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(STATE_VIBRATE, adapter.isVibrationEnabled)
outState.putInt(STATE_VIBRATION_PATTERN, adapter.vibrationPattern)
outState.putInt(STATE_VIBRATION_TIMES, adapter.vibrationTimes)
}
@ -81,15 +90,16 @@ class VibrationPatternDialogFragment : PreferenceDialogFragmentCompat() {
}
private inner class VibrationPatternAdapter(
var isVibrationEnabled: Boolean,
private val entries: List<String>,
private val entryValues: List<Int>,
initialVibrationPattern: Int,
initialVibrationTimes: Int
) : BaseAdapter() {
private var checkedPosition = entryValues.indexOf(initialVibrationPattern).takeIf { it != -1 } ?: 0
private var checkedEntryIndex = entryValues.indexOf(initialVibrationPattern).takeIf { it != -1 } ?: 0
val vibrationPattern: Int
get() = entryValues[checkedPosition]
get() = entryValues[checkedEntryIndex]
var vibrationTimes = initialVibrationTimes
@ -98,33 +108,61 @@ class VibrationPatternDialogFragment : PreferenceDialogFragmentCompat() {
override fun getItemId(position: Int): Long = position.toLong()
override fun getItemViewType(position: Int): Int {
return if (position < entries.size) 0 else 1
return when {
position == 0 -> 0
position.toEntryIndex() < entries.size -> 1
else -> 2
}
}
override fun getViewTypeCount(): Int = 2
override fun getViewTypeCount(): Int = 3
override fun getCount(): Int = entries.size + 1
override fun getCount(): Int = entries.size + 2
override fun getItem(position: Int): Any? {
return if (position < entries.size) entries[position] else null
return when {
position == 0 -> null
position.toEntryIndex() < entries.size -> entries[position.toEntryIndex()]
else -> null
}
}
private fun Int.toEntryIndex() = this - 1
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val itemType = getItemViewType(position)
return if (itemType == 0) {
getVibrationPatternView(position, convertView, parent)
} else {
getVibrationTimesView(convertView, parent)
return when (getItemViewType(position)) {
0 -> getVibrationSwitchView(convertView, parent)
1 -> getVibrationPatternView(position, convertView, parent)
2 -> getVibrationTimesView(convertView, parent)
else -> error("Unknown item type")
}
}
private fun getVibrationSwitchView(convertView: View?, parent: ViewGroup?): View {
return convertView.orInflate<View>(R.layout.preference_vibration_switch_item, parent)
.apply {
val switchButton = findViewById<SwitchCompat>(R.id.vibrationSwitch)
switchButton.isChecked = isVibrationEnabled
switchButton.setOnCheckedChangeListener { _, isChecked ->
isVibrationEnabled = isChecked
notifyDataSetChanged()
}
findViewById<View>(R.id.switchContainer).setOnClickListener {
switchButton.toggle()
}
}
}
private fun getVibrationPatternView(position: Int, convertView: View?, parent: ViewGroup?): View {
return convertView.orInflate<CheckedTextView>(R.layout.preference_vibration_pattern_item, parent)
.apply {
text = getItem(position) as String
isChecked = position == checkedPosition
val entryIndex = position.toEntryIndex()
isChecked = entryIndex == checkedEntryIndex
isEnabled = isVibrationEnabled
setOnClickListener {
checkedPosition = position
checkedEntryIndex = entryIndex
playVibration()
notifyDataSetChanged()
}
@ -136,7 +174,10 @@ class VibrationPatternDialogFragment : PreferenceDialogFragmentCompat() {
val vibrationTimesValue = findViewById<TextView>(R.id.vibrationTimesValue)
vibrationTimesValue.text = vibrationTimes.toString()
val vibrationTimesSeekBar = findViewById<SeekBar>(R.id.vibrationTimesSeekBar)
val vibrationTimesSeekBar = findViewById<SeekBar>(R.id.vibrationTimesSeekBar).apply {
isEnabled = isVibrationEnabled
}
val progress = vibrationTimes - 1
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
vibrationTimesSeekBar.setProgress(progress, false)
@ -167,6 +208,7 @@ class VibrationPatternDialogFragment : PreferenceDialogFragmentCompat() {
}
companion object {
private const val STATE_VIBRATE = "vibrate"
private const val STATE_VIBRATION_PATTERN = "vibrationPattern"
private const val STATE_VIBRATION_TIMES = "vibrationTimes"
}

View file

@ -6,13 +6,14 @@ import android.util.AttributeSet
import androidx.core.content.res.TypedArrayUtils
import androidx.preference.ListPreference
import com.fsck.k9.NotificationSetting
import com.fsck.k9.ui.R
import com.takisoft.preferencex.PreferenceFragmentCompat
/**
* Allows selecting a vibration pattern and specifying how often the vibration should repeat.
* Preference to configure the vibration pattern used for a notification (enable/disable, pattern, repeat count).
*/
@SuppressLint("RestrictedApi")
class VibrationPatternPreference
class VibrationPreference
@JvmOverloads
constructor(
context: Context,
@ -24,6 +25,9 @@ constructor(
),
defStyleRes: Int = 0
) : ListPreference(context, attrs, defStyleAttr, defStyleRes) {
internal var isVibrationEnabled: Boolean = false
private set
internal var vibrationPattern: Int = DEFAULT_VIBRATION_PATTERN
private set
@ -32,8 +36,9 @@ constructor(
override fun onSetInitialValue(defaultValue: Any?) {
val encoded = getPersistedString(defaultValue as String?)
val (vibrationPattern, vibrationTimes) = decode(encoded)
val (isVibrationEnabled, vibrationPattern, vibrationTimes) = decode(encoded)
this.isVibrationEnabled = isVibrationEnabled
this.vibrationPattern = vibrationPattern
this.vibrationTimes = vibrationTimes
@ -44,19 +49,20 @@ constructor(
preferenceManager.showDialog(this)
}
fun setVibrationPattern(vibrationPattern: Int, vibrationTimes: Int) {
fun setVibration(isVibrationEnabled: Boolean, vibrationPattern: Int, vibrationTimes: Int) {
this.isVibrationEnabled = isVibrationEnabled
this.vibrationPattern = vibrationPattern
this.vibrationTimes = vibrationTimes
val encoded = encode(vibrationPattern, vibrationTimes)
val encoded = encode(isVibrationEnabled, vibrationPattern, vibrationTimes)
persistString(encoded)
updateSummary()
}
fun setVibrationPatternFromSystem(combinedPattern: List<Long>?) {
fun setVibrationFromSystem(isVibrationEnabled: Boolean, combinedPattern: List<Long>?) {
if (combinedPattern == null || combinedPattern.size < 2 || combinedPattern.size % 2 != 0) {
setVibrationPattern(DEFAULT_VIBRATION_PATTERN, DEFAULT_VIBRATION_TIMES)
setVibration(isVibrationEnabled, DEFAULT_VIBRATION_PATTERN, DEFAULT_VIBRATION_TIMES)
return
}
@ -70,12 +76,16 @@ constructor(
testPattern.contentEquals(combinedPatternArray)
} ?: DEFAULT_VIBRATION_PATTERN
setVibrationPattern(vibrationPattern, vibrationTimes)
setVibration(isVibrationEnabled, vibrationPattern, vibrationTimes)
}
private fun updateSummary() {
val index = entryValues.indexOf(vibrationPattern.toString())
summary = entries[index]
summary = if (isVibrationEnabled) {
val index = entryValues.indexOf(vibrationPattern.toString())
entries[index]
} else {
context.getString(R.string.account_settings_vibrate_summary_disabled)
}
}
companion object {
@ -84,17 +94,20 @@ constructor(
init {
PreferenceFragmentCompat.registerPreferenceFragment(
VibrationPatternPreference::class.java, VibrationPatternDialogFragment::class.java
VibrationPreference::class.java, VibrationDialogFragment::class.java
)
}
fun encode(vibrationPattern: Int, vibrationTimes: Int): String {
return "$vibrationPattern|$vibrationTimes"
fun encode(isVibrationEnabled: Boolean, vibrationPattern: Int, vibrationTimes: Int): String {
return "$isVibrationEnabled|$vibrationPattern|$vibrationTimes"
}
fun decode(encoded: String): Pair<Int, Int> {
val (vibrationPattern, vibrationTimes) = encoded.split('|').map { it.toInt() }
return Pair(vibrationPattern, vibrationTimes)
fun decode(encoded: String): Triple<Boolean, Int, Int> {
val parts = encoded.split('|')
val isVibrationEnabled = parts[0].toBoolean()
val vibrationPattern = parts[1].toInt()
val vibrationTimes = parts[2].toInt()
return Triple(isVibrationEnabled, vibrationPattern, vibrationTimes)
}
}
}

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:id="@+id/switchContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/vibrationSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:focusable="false"
android:paddingStart="?attr/dialogPreferredPadding"
android:paddingTop="16dp"
android:paddingEnd="?attr/dialogPreferredPadding"
android:paddingBottom="16dp"
android:text="@string/account_settings_vibrate_enable"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorControlHighlight"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/switchContainer" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="12dp"
android:paddingStart="?attr/dialogPreferredPadding"
android:paddingEnd="?attr/dialogPreferredPadding"
android:text="@string/account_settings_vibrate_pattern_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?attr/textColorAlertDialogListItem"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/divider" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -654,8 +654,8 @@ Please submit bug reports, contribute new features and ask questions at
<string name="account_settings_description_label">Account name</string>
<string name="account_settings_name_label">Your name</string>
<string name="notifications_title">Notifications</string>
<string name="account_settings_vibration">Vibration</string>
<string name="account_settings_vibrate_enable">Vibrate</string>
<string name="account_settings_vibrate_summary">Vibrate when mail arrives</string>
<string name="account_settings_vibrate_pattern_label">Vibration pattern</string>
<string name="account_settings_vibrate_pattern_default">Default</string>
<string name="account_settings_vibrate_pattern_1">Pattern 1</string>
@ -664,6 +664,7 @@ Please submit bug reports, contribute new features and ask questions at
<string name="account_settings_vibrate_pattern_4">Pattern 4</string>
<string name="account_settings_vibrate_pattern_5">Pattern 5</string>
<string name="account_settings_vibrate_times">Repeat vibration</string>
<string name="account_settings_vibrate_summary_disabled">Disabled</string>
<string name="account_settings_ringtone">New mail ringtone</string>
<string name="account_settings_led_label">Blink LED</string>
<string name="account_settings_led_summary">Blink LED when mail arrives</string>

View file

@ -335,17 +335,10 @@
android:key="account_ringtone"
android:title="@string/account_settings_ringtone" />
<CheckBoxPreference
android:defaultValue="false"
<com.fsck.k9.ui.settings.account.VibrationPreference
android:dependency="account_notify"
android:key="account_vibrate"
android:summary="@string/account_settings_vibrate_summary"
android:title="@string/account_settings_vibrate_enable" />
<com.fsck.k9.ui.settings.account.VibrationPatternPreference
android:dependency="account_vibrate"
android:key="account_combined_vibration_pattern"
android:title="@string/account_settings_vibrate_pattern_label"
android:key="account_combined_vibration"
android:title="@string/account_settings_vibration"
android:entries="@array/vibrate_pattern_entries"
android:entryValues="@array/vibrate_pattern_values" />