Refactor device configuration UI and move troubleshooter

- Change "No primary device" card to "No device configured" with clearer messaging
- Replace troubleshoot action with "Manage devices" button that navigates to device manager
- Move troubleshooter from overview to Settings → Support section
- Add concise troubleshooter description for settings preference
- Update navigation to support troubleshooter access from settings
- Improve user experience by providing more intuitive device management flow
This commit is contained in:
darken
2025-09-29 13:36:21 +02:00
parent 0e3ee655ef
commit 15371e01ef
20 changed files with 499 additions and 11 deletions
@@ -0,0 +1,23 @@
package eu.darken.capod.devices.core
import android.os.Parcelable
import com.squareup.moshi.JsonClass
import eu.darken.capod.common.bluetooth.BluetoothAddress
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.apple.protocol.IdentityResolvingKey
import eu.darken.capod.pods.core.apple.protocol.ProximityEncryptionKey
import kotlinx.parcelize.Parcelize
import java.util.UUID
@Parcelize
@JsonClass(generateAdapter = true)
data class DeviceProfile(
val id: String = UUID.randomUUID().toString(),
val name: String,
val address: BluetoothAddress? = null,
val model: PodDevice.Model = PodDevice.Model.UNKNOWN,
val identityKey: IdentityResolvingKey? = null,
val encryptionKey: ProximityEncryptionKey? = null,
val minimumSignalQuality: Float = 0.20f,
val isEnabled: Boolean = true
) : Parcelable
@@ -0,0 +1,87 @@
package eu.darken.capod.devices.core
import android.content.Context
import android.content.SharedPreferences
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.capod.common.debug.logging.log
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class DeviceProfilesRepo @Inject constructor(
@ApplicationContext private val context: Context,
private val moshi: Moshi,
) {
private val preferences: SharedPreferences = context.getSharedPreferences("device_profiles", Context.MODE_PRIVATE)
private val _profiles = MutableStateFlow<List<DeviceProfile>>(emptyList())
val profiles: Flow<List<DeviceProfile>> = _profiles.asStateFlow()
private val listType = Types.newParameterizedType(List::class.java, DeviceProfile::class.java)
private val adapter = moshi.adapter<List<DeviceProfile>>(listType)
init {
loadProfiles()
}
private fun loadProfiles() {
val json = preferences.getString(KEY_PROFILES, null)
val loadedProfiles = if (json != null) {
try {
adapter.fromJson(json) ?: emptyList()
} catch (e: Exception) {
log(VERBOSE) { "Failed to load device profiles: $e" }
emptyList()
}
} else {
emptyList()
}
_profiles.value = loadedProfiles
log(VERBOSE) { "Loaded ${loadedProfiles.size} device profiles" }
}
private fun saveProfiles() {
val json = adapter.toJson(_profiles.value)
preferences.edit().putString(KEY_PROFILES, json).apply()
log(VERBOSE) { "Saved ${_profiles.value.size} device profiles" }
}
fun addProfile(profile: DeviceProfile) {
val updatedProfiles = _profiles.value.toMutableList()
updatedProfiles.add(profile)
_profiles.value = updatedProfiles
saveProfiles()
}
fun updateProfile(profile: DeviceProfile) {
val updatedProfiles = _profiles.value.toMutableList()
val index = updatedProfiles.indexOfFirst { it.id == profile.id }
if (index != -1) {
updatedProfiles[index] = profile
_profiles.value = updatedProfiles
saveProfiles()
}
}
fun removeProfile(profileId: String) {
val updatedProfiles = _profiles.value.toMutableList()
updatedProfiles.removeAll { it.id == profileId }
_profiles.value = updatedProfiles
saveProfiles()
}
fun getProfile(profileId: String): DeviceProfile? {
return _profiles.value.find { it.id == profileId }
}
companion object {
private const val KEY_PROFILES = "profiles"
}
}
+3 -2
View File
@@ -6,9 +6,10 @@
<string name="general_value_not_available_label">N/A</string>
<string name="general_error_label">Error</string>
<string name="general_grant_permission_action">Grant permission</string>
<string name="general_manage_devices_action">Manage devices</string>
<string name="overview_nomaindevice_label">No primary device</string>
<string name="overview_nomaindevice_description">All detected devices are unlikely to be yours. Power on and connect your device or adjust the settings.</string>
<string name="overview_nomaindevice_label">No device configured</string>
<string name="overview_nomaindevice_description">Configure your device to start monitoring battery levels and enable additional features.</string>
<string name="overview_bluetooth_disabled_label">Bluetooth is disabled</string>
<string name="overview_bluetooth_disabled_description">Bluetooth is disabled, enable it ;)</string>
@@ -0,0 +1,35 @@
package eu.darken.capod.devices.ui
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.viewbinding.ViewBinding
import eu.darken.capod.common.lists.BindableVH
import eu.darken.capod.common.lists.differ.AsyncDiffer
import eu.darken.capod.common.lists.differ.DifferItem
import eu.darken.capod.common.lists.differ.HasAsyncDiffer
import eu.darken.capod.common.lists.differ.setupDiffer
import eu.darken.capod.common.lists.modular.ModularAdapter
import eu.darken.capod.common.lists.modular.mods.DataBinderMod
import eu.darken.capod.common.lists.modular.mods.TypedVHCreatorMod
import javax.inject.Inject
class DeviceManagerAdapter @Inject constructor() :
ModularAdapter<DeviceManagerAdapter.BaseVH<DeviceManagerAdapter.Item, ViewBinding>>(),
HasAsyncDiffer<DeviceManagerAdapter.Item> {
override val asyncDiffer: AsyncDiffer<*, Item> = setupDiffer()
init {
modules.add(DataBinderMod(data))
modules.add(TypedVHCreatorMod({ data[it] is DeviceProfileVH.Item }) { DeviceProfileVH(it) })
}
override fun getItemCount(): Int = data.size
abstract class BaseVH<D : Item, B : ViewBinding>(
@LayoutRes layoutId: Int,
parent: ViewGroup
) : ModularAdapter.VH(layoutId, parent), BindableVH<D, B>
interface Item : DifferItem
}
@@ -0,0 +1,67 @@
package eu.darken.capod.devices.ui
import android.os.Bundle
import android.util.TypedValue
import android.view.View
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint
import eu.darken.capod.R
import eu.darken.capod.common.EdgeToEdgeHelper
import eu.darken.capod.common.lists.differ.update
import eu.darken.capod.common.lists.setupDefaults
import eu.darken.capod.common.uix.Fragment3
import eu.darken.capod.common.viewbinding.viewBinding
import eu.darken.capod.databinding.DeviceManagerFragmentBinding
import javax.inject.Inject
@AndroidEntryPoint
class DeviceManagerFragment : Fragment3(R.layout.device_manager_fragment) {
override val vm: DeviceManagerFragmentVM by viewModels()
override val ui: DeviceManagerFragmentBinding by viewBinding()
@Inject
lateinit var adapter: DeviceManagerAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
EdgeToEdgeHelper(requireActivity()).apply {
insetsPadding(ui.root, left = true, right = true)
insetsPadding(ui.toolbar, top = true)
}
// Handle FAB margins for edge-to-edge
ViewCompat.setOnApplyWindowInsetsListener(ui.fab) { view, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
val baseMargin = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 16f,
resources.displayMetrics
).toInt()
view.updateLayoutParams<androidx.constraintlayout.widget.ConstraintLayout.LayoutParams> {
bottomMargin = baseMargin + systemBars.bottom
marginEnd = baseMargin + systemBars.right
}
insets
}
ui.apply {
list.setupDefaults(adapter, dividers = false)
toolbar.setNavigationOnClickListener {
vm.onBackPressed()
}
fab.setOnClickListener {
vm.onAddDevice()
}
}
vm.listItems.observe2(ui) { items ->
adapter.update(items)
}
super.onViewCreated(view, savedInstanceState)
}
}
@@ -0,0 +1,60 @@
package eu.darken.capod.devices.ui
import androidx.lifecycle.LiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.asLiveData
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.darken.capod.common.coroutine.DispatcherProvider
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.uix.ViewModel3
import eu.darken.capod.devices.core.DeviceProfile
import eu.darken.capod.devices.core.DeviceProfilesRepo
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
@HiltViewModel
class DeviceManagerFragmentVM @Inject constructor(
@Suppress("UNUSED_PARAMETER") handle: SavedStateHandle,
dispatcherProvider: DispatcherProvider,
private val deviceProfilesRepo: DeviceProfilesRepo,
) : ViewModel3(dispatcherProvider = dispatcherProvider) {
val listItems: LiveData<List<DeviceManagerAdapter.Item>> = deviceProfilesRepo.profiles
.map { profiles ->
profiles.map { profile ->
DeviceProfileVH.Item(
profile = profile,
onItemClick = { onItemClick(it) },
onMenuClick = { onMenuClick(it) }
)
}
}
.onEach { log(TAG) { "Profiles updated: ${it.size} items" } }
.asLiveData()
fun onAddDevice() {
log(TAG) { "onAddDevice()" }
// TODO: Navigate to device profile creation screen
}
fun onBackPressed() {
log(TAG) { "onBackPressed()" }
navEvents.postValue(null)
}
private fun onItemClick(profile: DeviceProfile) {
log(TAG) { "onItemClick(): $profile" }
// TODO: Navigate to device profile edit screen
}
private fun onMenuClick(profile: DeviceProfile) {
log(TAG) { "onMenuClick(): $profile" }
// TODO: Show menu with edit/delete options
}
companion object {
private val TAG = logTag("DeviceManager", "ViewModel")
}
}
@@ -0,0 +1,42 @@
package eu.darken.capod.devices.ui
import android.view.ViewGroup
import eu.darken.capod.R
import eu.darken.capod.databinding.DeviceManagerItemBinding
import eu.darken.capod.devices.core.DeviceProfile
class DeviceProfileVH(parent: ViewGroup) :
DeviceManagerAdapter.BaseVH<DeviceProfileVH.Item, DeviceManagerItemBinding>(
R.layout.device_manager_item,
parent
) {
override val viewBinding = lazy { DeviceManagerItemBinding.bind(itemView) }
override val onBindData: DeviceManagerItemBinding.(
item: Item,
payloads: List<Any>
) -> Unit = { item, _ ->
val profile = item.profile
deviceName.text = profile.name
deviceDetails.text = buildString {
profile.address?.let {
append(it.toString())
append("")
}
append(profile.model.name)
}
itemView.setOnClickListener { item.onItemClick(profile) }
menuButton.setOnClickListener { item.onMenuClick(profile) }
}
data class Item(
val profile: DeviceProfile,
val onItemClick: (DeviceProfile) -> Unit,
val onMenuClick: (DeviceProfile) -> Unit,
) : DeviceManagerAdapter.Item {
override val stableId: Long = profile.id.hashCode().toLong()
}
}
@@ -60,6 +60,11 @@ class OverviewFragment : Fragment3(R.layout.main_fragment) {
ui.toolbar.apply {
setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_item_devices -> {
vm.goToDeviceManager()
true
}
R.id.menu_item_settings -> {
vm.goToSettings()
true
@@ -141,9 +141,11 @@ class OverviewFragmentVM @Inject constructor(
if (!isBluetoothEnabled) {
items.add(0, BluetoothDisabledVH.Item)
} else if (mainPod == null) {
items.add(0, MissingMainDeviceVH.Item {
OverviewFragmentDirections.actionOverviewFragmentToTroubleShooterFragment().navigate()
})
items.add(0, MissingMainDeviceVH.Item(
onManageDevices = {
OverviewFragmentDirections.actionOverviewFragmentToDeviceManagerFragment().navigate()
}
))
}
}
@@ -189,6 +191,10 @@ class OverviewFragmentVM @Inject constructor(
OverviewFragmentDirections.actionOverviewFragmentToSettingsFragment().navigate()
}
fun goToDeviceManager() = launch {
OverviewFragmentDirections.actionOverviewFragmentToDeviceManagerFragment().navigate()
}
fun onUpgrade() = launch {
val call: (Activity) -> Unit = {
upgradeRepo.launchBillingFlow(it)
@@ -21,11 +21,11 @@ class MissingMainDeviceVH(parent: ViewGroup) :
item: Item,
payloads: List<Any>
) -> Unit = binding(payload = true) { item ->
troubleshootAction.setOnClickListener { item.onTroubleShoot() }
manageDevicesAction.setOnClickListener { item.onManageDevices() }
}
data class Item(
val onTroubleShoot: () -> Unit,
val onManageDevices: () -> Unit,
) : OverviewAdapter.Item {
override val stableId: Long = Item::class.hashCode().toLong()
@@ -2,8 +2,10 @@ package eu.darken.capod.main.ui.settings
import android.os.Bundle
import android.view.View
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import dagger.hilt.android.AndroidEntryPoint
import eu.darken.capod.MainDirections
import eu.darken.capod.R
import eu.darken.capod.common.BuildConfigWrap
import eu.darken.capod.common.PrivacyPolicy
@@ -43,6 +45,10 @@ class SettingsIndexFragment : PreferenceFragment2() {
webpageTool.open(PrivacyPolicy.URL)
true
}
findPreference<Preference>("core.profile.manager")!!.setOnPreferenceClickListener {
findNavController().navigate(MainDirections.actionGlobalDeviceManagerFragment())
true
}
super.onPreferencesCreated()
}
@@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.View
import androidx.annotation.Keep
import androidx.fragment.app.viewModels
import androidx.navigation.findNavController
import androidx.preference.Preference
import dagger.hilt.android.AndroidEntryPoint
import eu.darken.capod.R
@@ -12,6 +13,7 @@ import eu.darken.capod.common.debug.recording.ui.RecorderConsentDialog
import eu.darken.capod.common.observe2
import eu.darken.capod.common.uix.PreferenceFragment3
import eu.darken.capod.main.core.GeneralSettings
import eu.darken.capod.main.ui.settings.SettingsFragmentDirections
import javax.inject.Inject
@Keep
@@ -28,6 +30,7 @@ class SupportFragment : PreferenceFragment3() {
@Inject lateinit var webpageTool: WebpageTool
private val debugLogPref by lazy { findPreference<Preference>("support.debuglog")!! }
private val troubleshooterPref by lazy { findPreference<Preference>("support.troubleshooter")!! }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
vm.recorderState.observe2(this) { state ->
@@ -55,6 +58,13 @@ class SupportFragment : PreferenceFragment3() {
true
}
}
troubleshooterPref.setOnPreferenceClickListener {
val navController = requireActivity().findNavController(R.id.nav_host)
navController.navigate(SettingsFragmentDirections.actionSettingsFragmentToTroubleShooterFragment())
true
}
super.onViewCreated(view, savedInstanceState)
}
}
@@ -0,0 +1,43 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Widget.Material3.Toolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
app:title="@string/settings_devices_label" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="0dp"
android:layout_height="0dp"
android:paddingBottom="80dp"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar"
tools:listitem="@layout/device_manager_item" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:contentDescription="Add device"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_baseline_add_24" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/device_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="16dp"
tools:src="@drawable/ic_baseline_devices_other_24" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/device_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?textAppearanceSubtitle1"
android:textStyle="bold"
tools:text="My AirPods Pro" />
<TextView
android:id="@+id/device_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?textAppearanceBody2"
android:textColor="?android:textColorSecondary"
tools:text="AA:BB:CC:DD:EE:FF • AirPods Pro" />
</LinearLayout>
<ImageView
android:id="@+id/menu_button"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="Menu"
android:src="@drawable/abc_ic_menu_overflow_material" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
@@ -33,18 +33,18 @@
android:layout_marginHorizontal="16dp"
android:layout_marginTop="4dp"
android:text="@string/overview_nomaindevice_description"
app:layout_constraintBottom_toTopOf="@id/troubleshoot_action"
app:layout_constraintBottom_toTopOf="@id/manage_devices_action"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/permission_label" />
<com.google.android.material.button.MaterialButton
android:id="@+id/troubleshoot_action"
android:id="@+id/manage_devices_action"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/troubleshoot_action"
android:text="@string/general_manage_devices_action"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/permission_description" />
+5
View File
@@ -16,6 +16,11 @@
android:visible="false"
tool:visible="true"
app:showAsAction="always" />
<item
android:id="@+id/menu_item_devices"
android:icon="@drawable/ic_baseline_devices_other_24"
android:title="@string/settings_devices_label"
app:showAsAction="always" />
<item
android:id="@+id/menu_item_settings"
android:icon="@drawable/ic_baseline_settings_24"
+17 -1
View File
@@ -13,6 +13,9 @@
<action
android:id="@+id/action_overviewFragment_to_settingsFragment"
app:destination="@id/settingsFragment" />
<action
android:id="@+id/action_overviewFragment_to_deviceManagerFragment"
app:destination="@id/deviceManagerFragment" />
<action
android:id="@+id/action_overviewFragment_to_troubleShooterFragment"
app:destination="@id/troubleShooterFragment" />
@@ -27,7 +30,16 @@
android:id="@+id/settingsFragment"
android:name="eu.darken.capod.main.ui.settings.SettingsFragment"
android:label="SettingsFragment"
tools:layout="@layout/settings_fragment" />
tools:layout="@layout/settings_fragment">
<action
android:id="@+id/action_settingsFragment_to_troubleShooterFragment"
app:destination="@id/troubleShooterFragment" />
</fragment>
<fragment
android:id="@+id/deviceManagerFragment"
android:name="eu.darken.capod.devices.ui.DeviceManagerFragment"
android:label="DeviceManagerFragment"
tools:layout="@layout/device_manager_fragment" />
<fragment
android:id="@+id/troubleShooterFragment"
android:name="eu.darken.capod.troubleshooter.ui.TroubleShooterFragment"
@@ -43,4 +55,8 @@
app:popUpTo="@id/onboardingFragment" />
</fragment>
<action
android:id="@+id/action_global_deviceManagerFragment"
app:destination="@id/deviceManagerFragment" />
</navigation>
+3
View File
@@ -40,6 +40,8 @@
<string name="settings_autoconnect_description">If Android does not automatically connect, we can ask it too. This will set the monitor mode setting to \'Always\'.</string>
<string name="settings_autoconnect_condition_label">Auto connect condition</string>
<string name="settings_autoconnect_condition_description">When should we try to connect to your device?</string>
<string name="settings_devices_label">Devices</string>
<string name="settings_devices_description">Manage your devices.</string>
<string name="settings_reaction_label">Reactions</string>
<string name="settings_reaction_description">React to events and behaviors.</string>
<string name="settings_category_yourdevice_label">Your device</string>
@@ -128,6 +130,7 @@
<string name="settings_compat_indirectcallback_summary">Use an alternative method to receive BLE data from the system (broadcast instead of callback).</string>
<string name="troubleshooter_title">Troubleshooter</string>
<string name="troubleshooter_summary">Diagnose and fix Bluetooth connectivity issues.</string>
<string name="troubleshooter_ble_intro_title">Bluetooth Low Energy Broadcasts</string>
<string name="troubleshooter_ble_intro_body1">AirPods (and similar headphones) broadcast status information using a BLE technology called \"advertisements\". Some phones don\'t implement this technology correctly. CAPod can attempt to fix this by trying different compatibility options until data is received. Start music playback on your headphones and place them close to your phone, then start the process.</string>
<string name="troubleshooter_ble_intro_start_action">Start troubleshooting</string>
@@ -7,6 +7,12 @@
app:summary="@string/settings_general_description"
app:title="@string/settings_general_label" />
<Preference
android:key="core.profile.manager"
android:icon="@drawable/ic_baseline_devices_other_24"
app:summary="@string/settings_devices_description"
app:title="@string/settings_devices_label" />
<Preference
android:icon="@drawable/ic_baseline_widgets_24"
app:fragment="eu.darken.capod.reaction.ui.ReactionSettingsFragment"
@@ -19,6 +19,11 @@
</eu.darken.capod.common.preferences.IntentPreference>
<PreferenceCategory app:title="@string/settings_category_other_label">
<Preference
android:icon="@drawable/ic_baseline_settings_24"
android:key="support.troubleshooter"
android:summary="@string/troubleshooter_summary"
android:title="@string/troubleshooter_title" />
<Preference
android:icon="@drawable/ic_baseline_bug_report_24"
android:key="support.debuglog"