Merge pull request #5831 from k9mail/vibration_pattern
Change UI to configure the notification vibration pattern
This commit is contained in:
commit
53112bc5fb
11 changed files with 346 additions and 44 deletions
|
@ -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))
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
)
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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" />
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue