Add configurable minimum signal quality

This commit is contained in:
darken
2022-01-13 19:49:23 +01:00
parent 8d5d90f457
commit c33faeed57
16 changed files with 270 additions and 15 deletions
@@ -0,0 +1,22 @@
package eu.darken.capod.common
import android.content.Context
import android.util.TypedValue
object UIConverter {
fun convertDpToPixels(context: Context, dp: Float): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
context.resources.displayMetrics
).toInt()
}
fun convertSpToPixels(context: Context, sp: Float): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
sp,
context.resources.displayMetrics
).toInt()
}
}
@@ -0,0 +1,100 @@
package eu.darken.capod.common.preferences
import android.content.Context
import android.content.res.TypedArray
import android.os.Parcelable
import android.util.AttributeSet
import androidx.annotation.PluralsRes
import androidx.preference.DialogPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import eu.darken.capod.R
import eu.darken.capod.common.preferences.PercentSliderPreferenceDialogFragment.Companion.newInstance
import kotlinx.parcelize.Parcelize
class PercentSliderPreference(context: Context?, attrs: AttributeSet?) : DialogPreference(context, attrs) {
@get:PluralsRes val sliderTextPluralsResource: Int
val min: Float
val max: Float
private var internalValue = 0f
private var internalValueSet = false
init {
val a = getContext().obtainStyledAttributes(attrs, R.styleable.PercentSliderPreference)
min = a.getFloat(R.styleable.PercentSliderPreference_pspMin, 0f)
max = a.getFloat(R.styleable.PercentSliderPreference_pspMax, 1f)
sliderTextPluralsResource = a.getResourceId(R.styleable.PercentSliderPreference_sliderText, 0)
a.recycle()
}
// Always persist/notify the first time.
var value: Float
get() = internalValue
set(value) {
// Always persist/notify the first time.
val changed = internalValue != value
if (changed || !internalValueSet) {
internalValue = value
internalValueSet = true
persistFloat(value)
if (changed) {
notifyChanged()
}
}
}
override fun onGetDefaultValue(a: TypedArray, index: Int): Int {
return a.getInteger(index, 0)
}
override fun onSetInitialValue(restoreValue: Boolean, defaultValue: Any?) {
value = if (restoreValue) getPersistedFloat(internalValue) else defaultValue as Float
}
override fun onSaveInstanceState(): Parcelable {
val superState = super.onSaveInstanceState()
if (isPersistent) {
// No need to save instance state since it's persistent
return superState
}
return SavedState(
value = value,
superState = superState,
)
}
override fun onRestoreInstanceState(state: Parcelable?) {
if (state?.javaClass != SavedState::class.java) {
// Didn't save state for us in onSaveInstanceState
return super.onRestoreInstanceState(state)
}
val myState = state as SavedState
super.onRestoreInstanceState(myState.superState)
value = myState.value
}
@Parcelize
data class SavedState(
val value: Float,
val superState: Parcelable,
) : Parcelable
companion object {
private const val DIALOG_FRAGMENT_TAG = "android.support.v7.preference.PreferenceFragment.DIALOG"
fun onDisplayPreferenceDialog(preferenceFragment: PreferenceFragmentCompat, preference: Preference): Boolean {
if (preference is PercentSliderPreference) {
val fragmentManager = preferenceFragment.fragmentManager
if (fragmentManager!!.findFragmentByTag(DIALOG_FRAGMENT_TAG) == null) {
val dialogFragment = newInstance(preference.getKey())
dialogFragment.setTargetFragment(preferenceFragment, 0)
dialogFragment.show(fragmentManager, DIALOG_FRAGMENT_TAG)
}
return true
}
return false
}
}
}
@@ -0,0 +1,91 @@
package eu.darken.capod.common.preferences
import android.os.Bundle
import android.view.Gravity
import android.widget.LinearLayout
import android.widget.LinearLayout.*
import android.widget.SeekBar
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.preference.PreferenceDialogFragmentCompat
import eu.darken.capod.common.UIConverter
import kotlin.math.roundToInt
class PercentSliderPreferenceDialogFragment : PreferenceDialogFragmentCompat(), SeekBar.OnSeekBarChangeListener {
private val layoutContainer by lazy { LinearLayout(requireContext()) }
private val valueText by lazy { TextView(requireContext()) }
private val splashText by lazy { TextView(requireContext()) }
private val seekBar by lazy { SeekBar(requireContext()) }
private val preferencePercent: PercentSliderPreference
get() = super.getPreference() as PercentSliderPreference
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
layoutContainer.apply {
orientation = VERTICAL
val px: Int = UIConverter.convertDpToPixels(requireContext(), 24f)
setPadding(px, 0, px, 0)
}
splashText.apply {
if (preferencePercent.dialogMessage != null) splashText.text = preferencePercent.dialogMessage
layoutContainer.addView(this)
}
valueText.apply {
gravity = Gravity.CENTER_HORIZONTAL
textSize = 32f
layoutContainer.addView(this, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
}
seekBar.apply {
setOnSeekBarChangeListener(this@PercentSliderPreferenceDialogFragment)
max = (preferencePercent.max * 100).roundToInt()
progress = (preferencePercent.value * 100).roundToInt()
layoutContainer.addView(this, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
}
updateValueText()
builder.setView(layoutContainer)
builder.setNegativeButton(null, null)
super.onPrepareDialogBuilder(builder)
}
override fun onDialogClosed(positiveResult: Boolean) {
if (!positiveResult) return
val value: Int = seekBar.progress
if (preferencePercent.callChangeListener(value)) {
preferencePercent.value = value / 100f
}
}
override fun onProgressChanged(seek: SeekBar, value: Int, fromTouch: Boolean) {
if (value < preferencePercent.min) {
seek.progress = (preferencePercent.min * 100).roundToInt()
}
updateValueText()
}
private fun updateValueText() {
val count: Int = seekBar.progress
valueText.text = if (preferencePercent.sliderTextPluralsResource != 0) {
resources.getQuantityString(preferencePercent.sliderTextPluralsResource, count, count)
} else {
"$count%"
}
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
companion object {
@JvmStatic fun newInstance(key: String): PercentSliderPreferenceDialogFragment {
val fragment = PercentSliderPreferenceDialogFragment()
val arguments = Bundle()
arguments.putString(ARG_KEY, key)
fragment.arguments = arguments
return fragment
}
}
}
@@ -48,12 +48,18 @@ class GeneralSettings @Inject constructor(
false
)
val minimumSignalQuality = preferences.createFlowPreference(
"core.signal.minimum",
0.25f
)
override val preferenceDataStore: PreferenceDataStore = PreferenceStoreMapper(
monitorMode,
scannerMode,
autoPause,
autoPlay,
showAll,
minimumSignalQuality,
debugSettings.isAutoReportEnabled,
)
}
@@ -7,6 +7,7 @@ import eu.darken.capod.common.lists.binding
import eu.darken.capod.databinding.OverviewPodsAppleSingleBasicItemBinding
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
import eu.darken.capod.pods.core.getBatteryLevelHeadset
import eu.darken.capod.pods.core.getSignalQuality
import eu.darken.capod.pods.core.lastSeenFormatted
import java.time.Instant
@@ -4,14 +4,11 @@ import android.view.ViewGroup
import eu.darken.capod.R
import eu.darken.capod.common.lists.binding
import eu.darken.capod.databinding.OverviewPodsAppleDualItemBinding
import eu.darken.capod.pods.core.*
import eu.darken.capod.pods.core.HasDualPods.Pod
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.DualApplePods.DeviceColor
import eu.darken.capod.pods.core.apple.DualApplePods.LidState
import eu.darken.capod.pods.core.getBatteryLevelCase
import eu.darken.capod.pods.core.getBatteryLevelLeftPod
import eu.darken.capod.pods.core.getBatteryLevelRightPod
import eu.darken.capod.pods.core.lastSeenFormatted
import java.time.Instant
class DualApplePodsCardVH(parent: ViewGroup) :
@@ -7,6 +7,7 @@ import eu.darken.capod.common.lists.binding
import eu.darken.capod.databinding.OverviewPodsAppleSingleItemBinding
import eu.darken.capod.pods.core.apple.SingleApplePods
import eu.darken.capod.pods.core.getBatteryLevelHeadset
import eu.darken.capod.pods.core.getSignalQuality
import eu.darken.capod.pods.core.lastSeenFormatted
import java.time.Instant
@@ -5,6 +5,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.lists.binding
import eu.darken.capod.databinding.OverviewPodsUnknownItemBinding
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.getSignalQuality
import eu.darken.capod.pods.core.lastSeenFormatted
import java.time.Instant
@@ -3,8 +3,10 @@ package eu.darken.capod.main.ui.settings.general
import androidx.annotation.Keep
import androidx.fragment.app.viewModels
import androidx.preference.ListPreference
import androidx.preference.Preference
import dagger.hilt.android.AndroidEntryPoint
import eu.darken.capod.R
import eu.darken.capod.common.preferences.PercentSliderPreference
import eu.darken.capod.common.uix.PreferenceFragment2
import eu.darken.capod.main.core.GeneralSettings
import eu.darken.capod.main.core.MonitorMode
@@ -38,4 +40,10 @@ class GeneralSettingsFragment : PreferenceFragment2() {
}
super.onPreferencesCreated()
}
override fun onDisplayPreferenceDialog(preference: Preference) {
if (PercentSliderPreference.onDisplayPreferenceDialog(this, preference)) return
super.onDisplayPreferenceDialog(preference)
}
}
@@ -93,7 +93,8 @@ class PodMonitor @Inject constructor(
val mainDevice: Flow<PodDevice?>
get() = devices.map { devices ->
devices.maxByOrNull { it.rssi }?.let {
if (it.rssi > -85) it else null
val minimumSignalQuality = generalSettings.minimumSignalQuality.value
if (it.signalQuality > minimumSignalQuality) it else null
}
}
@@ -19,16 +19,18 @@ interface PodDevice {
val rssi: Int
get() = scanResult.rssi
/**
* This is not correct but it works ¯\_(ツ)_/¯
* The range of the RSSI is device specific (ROMs).
*/
val signalQuality: Float
get() = (100 - abs(rssi)) / 100f
val rawData: ByteArray
val rawDataHex: String
get() = rawData.joinToString(separator = " ") { String.format("%02X", it) }
fun getSignalQuality(context: Context): String {
val percentage = (100 - abs(rssi))
return "~$percentage%"
}
fun getLabel(context: Context): String
@get:DrawableRes
@@ -23,6 +23,11 @@ fun HasSinglePod.getBatteryLevelHeadset(context: Context): String =
batteryHeadsetPercent?.let { "${(it * 100).roundToInt()}%" }
?: context.getString(R.string.general_value_not_available_label)
fun PodDevice.getSignalQuality(context: Context): String {
val percentage = 100 * signalQuality
return "~${percentage.roundToInt()}%"
}
private val lastSeenFormatter = RelativeDateTimeFormatter.getInstance()
fun PodDevice.lastSeenFormatted(now: Instant): String {
@@ -4,7 +4,7 @@
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M17,4h3v16h-3zM5,14h3v6L5,20zM11,9h3v11h-3z" />
<path
android:fillColor="@android:color/white"
android:pathData="M17,4h3v16h-3zM5,14h3v6L5,20zM11,9h3v11h-3z" />
</vector>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PercentSliderPreference">
<attr name="pspMin" format="float" />
<attr name="pspMax" format="float" />
<attr name="sliderText" format="reference" />
</declare-styleable>
</resources>
+2
View File
@@ -115,4 +115,6 @@
<string name="pods_charging_label">Charging</string>
<string name="pods_inear_label">In ear</string>
<string name="pods_microphone_label">Microphone</string>
<string name="settings_signal_minimum_label">Minimum signal quality</string>
<string name="settings_signal_minimum_description">The minimum signal quality that a device needs to have to be considered yours.</string>
</resources>
+11 -2
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ListPreference
android:icon="@drawable/ic_baseline_disabled_visible_24"
@@ -31,6 +32,14 @@
android:summary="@string/settings_showall_description"
android:title="@string/settings_showall_label" />
<eu.darken.capod.common.preferences.PercentSliderPreference
android:icon="@drawable/ic_baseline_signal_cellular_alt_24"
android:key="core.signal.minimum"
android:summary="@string/settings_signal_minimum_description"
android:title="@string/settings_signal_minimum_label"
app:pspMax="0.9"
app:pspMin="0.1" />
<PreferenceCategory android:title="@string/settings_category_other_label">
<CheckBoxPreference
@@ -40,9 +49,9 @@
android:title="@string/settings_debug_autoreports_label" />
<Preference
android:fragment="eu.darken.capod.main.ui.settings.general.debug.DebugSettingsFragment"
android:icon="@drawable/ic_baseline_bug_report_24"
android:key="debug.settings"
android:fragment="eu.darken.capod.main.ui.settings.general.debug.DebugSettingsFragment"
android:summary="@string/settings_debug_description"
android:title="@string/settings_debug_label" />