Merge pull request #5831 from k9mail/vibration_pattern

Change UI to configure the notification vibration pattern
This commit is contained in:
cketti 2021-12-22 02:09:39 +01:00 committed by GitHub
commit 53112bc5fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 346 additions and 44 deletions

View file

@ -217,7 +217,7 @@ public class AccountSettingsDescriptions {
new V(1, new IntegerResourceSetting(0, R.array.vibrate_pattern_values))
));
s.put("vibrateTimes", Settings.versions(
new V(1, new IntegerResourceSetting(5, R.array.vibrate_times_label))
new V(1, new IntegerRangeSetting(1, 10, 5))
));
s.put("allowRemoteSearch", Settings.versions(
new V(18, new BooleanSetting(true))

View file

@ -212,19 +212,6 @@
<item>5</item>
</string-array>
<string-array name="vibrate_times_label" translatable="false">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
</string-array>
<string-array name="quote_style_values" translatable="false">
<item>PREFIX</item>
<item>HEADER</item>

View file

@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.fsck.k9.ui">
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:theme="@style/Theme.K9.Light"
android:supportsRtl="true" />

View file

@ -136,8 +136,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_vibrate_pattern" -> account.notificationSetting.vibratePattern.toString()
"account_vibrate_times" -> account.notificationSetting.vibrateTimes.toString()
"account_combined_vibration_pattern" -> getCombinedVibrationPattern()
"account_remote_search_num_results" -> account.remoteSearchNumResults.toString()
"account_ringtone" -> account.notificationSetting.ringtone
else -> defValue
@ -181,8 +180,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_vibrate_pattern" -> account.notificationSetting.vibratePattern = value.toInt()
"account_vibrate_times" -> account.notificationSetting.vibrateTimes = value.toInt()
"account_combined_vibration_pattern" -> setCombinedVibrationPattern(value)
"account_remote_search_num_results" -> account.remoteSearchNumResults = value.toInt()
"account_ringtone" -> with(account.notificationSetting) {
isRingEnabled = true
@ -248,4 +246,17 @@ class AccountSettingsDataStore(
return prefix + (specialFolderId?.toString() ?: FolderListPreference.NO_FOLDER_VALUE)
}
private fun getCombinedVibrationPattern(): String {
return VibrationPatternPreference.encode(
vibrationPattern = account.notificationSetting.vibratePattern,
vibrationTimes = account.notificationSetting.vibrateTimes
)
}
private fun setCombinedVibrationPattern(value: String) {
val (vibrationPattern, vibrationTimes) = VibrationPatternPreference.decode(value)
account.notificationSetting.vibratePattern = vibrationPattern
account.notificationSetting.vibrateTimes = vibrationTimes
}
}

View file

@ -420,8 +420,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
private val PRE_SDK26_NOTIFICATION_PREFERENCES = arrayOf(
"account_ringtone",
"account_vibrate",
"account_vibrate_pattern",
"account_vibrate_times",
"account_combined_vibration_pattern",
"account_led",
)

View file

@ -0,0 +1,173 @@
package com.fsck.k9.ui.settings.account
import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.os.VibrationEffect
import android.os.Vibrator
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.CheckedTextView
import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.content.getSystemService
import androidx.preference.PreferenceDialogFragmentCompat
import com.fsck.k9.NotificationSetting
import com.fsck.k9.ui.R
class VibrationPatternDialogFragment : PreferenceDialogFragmentCompat() {
private val vibrator by lazy { requireContext().getSystemService<Vibrator>() ?: error("Vibrator service missing") }
private val vibratePatternPreference: VibrationPatternPreference
get() = preference as VibrationPatternPreference
private lateinit var adapter: VibrationPatternAdapter
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext()
val vibrationPattern: Int
val vibrationTimes: Int
if (savedInstanceState != null) {
vibrationPattern = savedInstanceState.getInt(STATE_VIBRATION_PATTERN)
vibrationTimes = savedInstanceState.getInt(STATE_VIBRATION_TIMES)
} else {
vibrationPattern = vibratePatternPreference.vibrationPattern
vibrationTimes = vibratePatternPreference.vibrationTimes
}
adapter = VibrationPatternAdapter(
entries = vibratePatternPreference.entries.map { it.toString() },
entryValues = vibratePatternPreference.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)
.create()
}
override fun onDialogClosed(positiveResult: Boolean) {
if (positiveResult) {
vibratePatternPreference.setVibrationPattern(adapter.vibrationPattern, adapter.vibrationTimes)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(STATE_VIBRATION_PATTERN, adapter.vibrationPattern)
outState.putInt(STATE_VIBRATION_TIMES, adapter.vibrationTimes)
}
private fun playVibration() {
val vibrationPattern = adapter.vibrationPattern
val vibrationTimes = adapter.vibrationTimes
val combinedPattern = NotificationSetting.getVibration(vibrationPattern, vibrationTimes)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val vibrationEffect = VibrationEffect.createWaveform(combinedPattern, -1)
vibrator.vibrate(vibrationEffect)
} else {
@Suppress("DEPRECATION")
vibrator.vibrate(combinedPattern, -1)
}
}
private inner class VibrationPatternAdapter(
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
val vibrationPattern: Int
get() = entryValues[checkedPosition]
var vibrationTimes = initialVibrationTimes
override fun hasStableIds(): Boolean = true
override fun getItemId(position: Int): Long = position.toLong()
override fun getItemViewType(position: Int): Int {
return if (position < entries.size) 0 else 1
}
override fun getViewTypeCount(): Int = 2
override fun getCount(): Int = entries.size + 1
override fun getItem(position: Int): Any? {
return if (position < entries.size) entries[position] else null
}
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)
}
}
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
setOnClickListener {
checkedPosition = position
playVibration()
notifyDataSetChanged()
}
}
}
private fun getVibrationTimesView(convertView: View?, parent: ViewGroup?): View {
return convertView.orInflate<View>(R.layout.preference_vibration_times_item, parent).apply {
val vibrationTimesValue = findViewById<TextView>(R.id.vibrationTimesValue)
vibrationTimesValue.text = vibrationTimes.toString()
val vibrationTimesSeekBar = findViewById<SeekBar>(R.id.vibrationTimesSeekBar)
val progress = vibrationTimes - 1
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
vibrationTimesSeekBar.setProgress(progress, false)
} else {
vibrationTimesSeekBar.progress = progress
}
vibrationTimesSeekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
vibrationTimes = progress + 1
vibrationTimesValue.text = vibrationTimes.toString()
}
override fun onStartTrackingTouch(seekBar: SeekBar) = Unit
override fun onStopTrackingTouch(seekBar: SeekBar) {
playVibration()
}
})
}
}
@Suppress("UNCHECKED_CAST")
private fun <T : View> View?.orInflate(layoutResId: Int, parent: ViewGroup?): T {
val view = this ?: layoutInflater.inflate(layoutResId, parent, false)
return view as T
}
}
companion object {
private const val STATE_VIBRATION_PATTERN = "vibrationPattern"
private const val STATE_VIBRATION_TIMES = "vibrationTimes"
}
}

View file

@ -0,0 +1,77 @@
package com.fsck.k9.ui.settings.account
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import androidx.core.content.res.TypedArrayUtils
import androidx.preference.ListPreference
import com.takisoft.preferencex.PreferenceFragmentCompat
/**
* Allows selecting a vibration pattern and specifying how often the vibration should repeat.
*/
@SuppressLint("RestrictedApi")
class VibrationPatternPreference
@JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = TypedArrayUtils.getAttr(
context,
androidx.preference.R.attr.preferenceStyle,
android.R.attr.preferenceStyle
),
defStyleRes: Int = 0
) : ListPreference(context, attrs, defStyleAttr, defStyleRes) {
internal var vibrationPattern: Int = 0
private set
internal var vibrationTimes: Int = 1
private set
override fun onSetInitialValue(defaultValue: Any?) {
val encoded = getPersistedString(defaultValue as String?)
val (vibrationPattern, vibrationTimes) = decode(encoded)
this.vibrationPattern = vibrationPattern
this.vibrationTimes = vibrationTimes
updateSummary()
}
override fun onClick() {
preferenceManager.showDialog(this)
}
fun setVibrationPattern(vibrationPattern: Int, vibrationTimes: Int) {
this.vibrationPattern = vibrationPattern
this.vibrationTimes = vibrationTimes
val encoded = encode(vibrationPattern, vibrationTimes)
persistString(encoded)
updateSummary()
}
private fun updateSummary() {
val index = entryValues.indexOf(vibrationPattern.toString())
summary = entries[index]
}
companion object {
init {
PreferenceFragmentCompat.registerPreferenceFragment(
VibrationPatternPreference::class.java, VibrationPatternDialogFragment::class.java
)
}
fun encode(vibrationPattern: Int, vibrationTimes: Int): String {
return "$vibrationPattern|$vibrationTimes"
}
fun decode(encoded: String): Pair<Int, Int> {
val (vibrationPattern, vibrationTimes) = encoded.split('|').map { it.toInt() }
return Pair(vibrationPattern, vibrationTimes)
}
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:drawableStart="?android:attr/listChoiceIndicatorSingle"
android:drawablePadding="20dp"
android:ellipsize="marquee"
android:focusable="true"
android:gravity="center_vertical"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:paddingStart="@dimen/abc_select_dialog_padding_start_material"
android:paddingEnd="?attr/dialogPreferredPadding"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?attr/textColorAlertDialogListItem"
tools:text="Pattern 1" />

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/vibrationTimesValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="?attr/dialogPreferredPadding"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?attr/textColorAlertDialogListItem"
tools:text="2" />
<TextView
android:id="@+id/vibrationTimesTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/vibrationTimesValue"
android:layout_alignParentStart="true"
android:layout_marginStart="?attr/dialogPreferredPadding"
android:layout_marginTop="16dp"
android:layout_toStartOf="@id/vibrationTimesValue"
android:text="@string/account_settings_vibrate_times"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?attr/textColorAlertDialogListItem" />
<SeekBar
android:id="@+id/vibrationTimesSeekBar"
style="@style/Widget.AppCompat.SeekBar.Discrete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/vibrationTimesTitle"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_marginStart="?attr/dialogPreferredPadding"
android:layout_marginTop="16dp"
android:layout_marginEnd="?attr/dialogPreferredPadding"
android:layout_marginBottom="20dp"
android:max="9"
tools:progress="1" />
</RelativeLayout>

View file

@ -655,13 +655,13 @@ Please submit bug reports, contribute new features and ask questions at
<string name="notifications_title">Notifications</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 patterns</string>
<string name="account_settings_vibrate_pattern_default">default</string>
<string name="account_settings_vibrate_pattern_1">pattern 1</string>
<string name="account_settings_vibrate_pattern_2">pattern 2</string>
<string name="account_settings_vibrate_pattern_3">pattern 3</string>
<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_pattern_label">Vibration pattern</string>
<string name="account_settings_vibrate_pattern_default">Default</string>
<string name="account_settings_vibrate_pattern_1">Pattern 1</string>
<string name="account_settings_vibrate_pattern_2">Pattern 2</string>
<string name="account_settings_vibrate_pattern_3">Pattern 3</string>
<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_ringtone">New mail ringtone</string>
<string name="account_settings_led_label">Blink LED</string>

View file

@ -342,25 +342,12 @@
android:summary="@string/account_settings_vibrate_summary"
android:title="@string/account_settings_vibrate_enable" />
<ListPreference
<com.fsck.k9.ui.settings.account.VibrationPatternPreference
android:dependency="account_vibrate"
android:dialogTitle="@string/account_settings_vibrate_pattern_label"
android:key="account_combined_vibration_pattern"
android:title="@string/account_settings_vibrate_pattern_label"
android:entries="@array/vibrate_pattern_entries"
android:entryValues="@array/vibrate_pattern_values"
android:key="account_vibrate_pattern"
android:layout="?android:attr/preferenceLayoutChild"
app:useSimpleSummaryProvider="true"
android:title="@string/account_settings_vibrate_pattern_label" />
<ListPreference
android:dependency="account_vibrate"
android:dialogTitle="@string/account_settings_vibrate_times"
android:entries="@array/vibrate_times_label"
android:entryValues="@array/vibrate_times_label"
android:key="account_vibrate_times"
android:layout="?android:attr/preferenceLayoutChild"
app:useSimpleSummaryProvider="true"
android:title="@string/account_settings_vibrate_times" />
android:entryValues="@array/vibrate_pattern_values" />
<CheckBoxPreference
android:defaultValue="true"