mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
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:
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user