mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Add configurable minimum signal quality
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+91
@@ -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,
|
||||
)
|
||||
}
|
||||
+1
@@ -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
|
||||
|
||||
|
||||
+1
-4
@@ -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
|
||||
|
||||
|
||||
+1
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user