Skip to content

Commit 2c8e758

Browse files
authored
Make gestures configurable (+ breaking: swap left/right) (#5512)
* Add configurable gestures - Add a setting for gesture actions depending on the directions and number of pointers. (UI to follow later.) - Change default for left/right swipe with 3 pointers to match iOS. * Add settings screen for gestures - Add a new "Gestures" screen to configure the configurable webview gestures. - Change gestures to a list of enums so all possible combinations are known in advance (and we can do migrations if needed). - Extract list subheader to SettingsSubheader composable * Screenshot tests for new common composables * Add another enum for GesturePointers * Add tests for gesture defaults/none/customized
1 parent 3b476aa commit 2c8e758

30 files changed

Lines changed: 679 additions & 114 deletions

File tree

Loading
Loading
Loading
Loading
Loading

app/src/main/kotlin/io/homeassistant/companion/android/settings/SettingsFragment.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import io.homeassistant.companion.android.nfc.NfcSetupActivity
3636
import io.homeassistant.companion.android.onboarding.OnboardApp
3737
import io.homeassistant.companion.android.settings.controls.ManageControlsSettingsFragment
3838
import io.homeassistant.companion.android.settings.developer.DeveloperSettingsFragment
39+
import io.homeassistant.companion.android.settings.gestures.GesturesFragment
3940
import io.homeassistant.companion.android.settings.language.LanguagesProvider
4041
import io.homeassistant.companion.android.settings.notification.NotificationChannelFragment
4142
import io.homeassistant.companion.android.settings.notification.NotificationHistoryFragment
@@ -161,6 +162,14 @@ class SettingsFragment(
161162
}
162163
}
163164

165+
findPreference<Preference>("gestures")?.setOnPreferenceClickListener {
166+
parentFragmentManager.commit {
167+
replace(R.id.content, GesturesFragment::class.java, null)
168+
addToBackStack(getString(commonR.string.gestures))
169+
}
170+
return@setOnPreferenceClickListener true
171+
}
172+
164173
findPreference<ListPreference>("page_zoom")?.let {
165174
// The list of percentages for iOS/Android should match
166175
// https://github.com/home-assistant/iOS/blob/ff66bbf2e3f9add0abb0b492499b81e824db36ed/Sources/Shared/Settings/SettingsStore.swift#L108
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.homeassistant.companion.android.settings.gestures
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.compose.ui.platform.ComposeView
8+
import androidx.fragment.app.Fragment
9+
import androidx.fragment.app.viewModels
10+
import dagger.hilt.android.AndroidEntryPoint
11+
import io.homeassistant.companion.android.common.R as commonR
12+
import io.homeassistant.companion.android.settings.gestures.views.GesturesView
13+
import io.homeassistant.companion.android.util.compose.HomeAssistantAppTheme
14+
import kotlin.getValue
15+
16+
@AndroidEntryPoint
17+
class GesturesFragment : Fragment() {
18+
19+
val viewModel: GesturesViewModel by viewModels()
20+
21+
override fun onCreateView(
22+
inflater: LayoutInflater,
23+
container: ViewGroup?,
24+
savedInstanceState: Bundle?,
25+
): View {
26+
return ComposeView(requireContext()).apply {
27+
setContent {
28+
HomeAssistantAppTheme {
29+
GesturesView(
30+
gestureActions = viewModel.gestureActions,
31+
onSetAction = viewModel::setGestureAction,
32+
)
33+
}
34+
}
35+
}
36+
}
37+
38+
override fun onResume() {
39+
super.onResume()
40+
activity?.title = getString(commonR.string.gestures)
41+
}
42+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.homeassistant.companion.android.settings.gestures
2+
3+
import androidx.compose.runtime.mutableStateMapOf
4+
import androidx.lifecycle.ViewModel
5+
import androidx.lifecycle.viewModelScope
6+
import dagger.hilt.android.lifecycle.HiltViewModel
7+
import io.homeassistant.companion.android.common.data.prefs.PrefsRepository
8+
import io.homeassistant.companion.android.common.util.GestureAction
9+
import io.homeassistant.companion.android.common.util.HAGesture
10+
import javax.inject.Inject
11+
import kotlinx.coroutines.launch
12+
13+
@HiltViewModel
14+
class GesturesViewModel @Inject constructor(
15+
private val prefsRepository: PrefsRepository,
16+
) : ViewModel() {
17+
18+
val gestureActions = mutableStateMapOf<HAGesture, GestureAction>()
19+
20+
init {
21+
viewModelScope.launch {
22+
HAGesture.entries.forEach {
23+
gestureActions[it] = prefsRepository.getGestureAction(it)
24+
}
25+
}
26+
}
27+
28+
fun setGestureAction(gesture: HAGesture, action: GestureAction) {
29+
viewModelScope.launch {
30+
prefsRepository.setGestureAction(gesture, action)
31+
gestureActions[gesture] = action
32+
}
33+
}
34+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package io.homeassistant.companion.android.settings.gestures.views
2+
3+
import androidx.compose.foundation.layout.Box
4+
import androidx.compose.foundation.layout.PaddingValues
5+
import androidx.compose.foundation.layout.padding
6+
import androidx.compose.foundation.lazy.LazyColumn
7+
import androidx.compose.foundation.lazy.items
8+
import androidx.compose.material.Divider
9+
import androidx.compose.material.DropdownMenu
10+
import androidx.compose.material.DropdownMenuItem
11+
import androidx.compose.material.Text
12+
import androidx.compose.runtime.Composable
13+
import androidx.compose.runtime.getValue
14+
import androidx.compose.runtime.mutableStateOf
15+
import androidx.compose.runtime.remember
16+
import androidx.compose.runtime.setValue
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.res.stringResource
19+
import androidx.compose.ui.tooling.preview.Preview
20+
import androidx.compose.ui.unit.DpOffset
21+
import androidx.compose.ui.unit.dp
22+
import io.homeassistant.companion.android.common.R
23+
import io.homeassistant.companion.android.common.util.GestureAction
24+
import io.homeassistant.companion.android.common.util.HAGesture
25+
import io.homeassistant.companion.android.settings.views.SettingsRow
26+
import io.homeassistant.companion.android.settings.views.SettingsSubheader
27+
import io.homeassistant.companion.android.util.plus
28+
import io.homeassistant.companion.android.util.safeBottomPaddingValues
29+
30+
@Composable
31+
fun GesturesView(
32+
gestureActions: Map<HAGesture, GestureAction>,
33+
onSetAction: (HAGesture, GestureAction) -> Unit,
34+
) {
35+
LazyColumn(
36+
contentPadding = PaddingValues(vertical = 16.dp) + safeBottomPaddingValues(applyHorizontal = false),
37+
) {
38+
item {
39+
Text(
40+
text = stringResource(R.string.gestures_description),
41+
modifier = Modifier.padding(horizontal = 16.dp),
42+
)
43+
Divider(
44+
modifier = Modifier.padding(all = 16.dp),
45+
)
46+
}
47+
48+
val gesturesGrouped = HAGesture.entries.groupBy { it.direction }
49+
gesturesGrouped.forEach { (direction, gestures) ->
50+
item {
51+
SettingsSubheader(stringResource(direction.description))
52+
}
53+
items(gestures) { gesture ->
54+
GestureSettingRow(
55+
gesture = gesture,
56+
action = gestureActions[gesture],
57+
onSetAction = { action -> onSetAction(gesture, action) },
58+
)
59+
}
60+
}
61+
}
62+
}
63+
64+
@Composable
65+
private fun GestureSettingRow(
66+
gesture: HAGesture,
67+
action: GestureAction?,
68+
onSetAction: (GestureAction) -> Unit,
69+
) {
70+
var expanded by remember { mutableStateOf(false) }
71+
72+
Box {
73+
SettingsRow(
74+
primaryText = stringResource(gesture.pointers.description),
75+
secondaryText = action?.let { stringResource(it.description) } ?: "",
76+
icon = null,
77+
onClicked = { expanded = true },
78+
)
79+
DropdownMenu(
80+
expanded = expanded,
81+
onDismissRequest = { expanded = false },
82+
// Prevent dropdown menu hitting screen edge which looks wrong
83+
offset = DpOffset(x = 8.dp, y = 0.dp),
84+
) {
85+
GestureAction.entries.forEach { action ->
86+
DropdownMenuItem({
87+
onSetAction(action)
88+
expanded = false
89+
}) {
90+
Text(stringResource(action.description))
91+
}
92+
}
93+
}
94+
}
95+
}
96+
97+
@Preview
98+
@Composable
99+
private fun PreviewGesturesView() {
100+
GesturesView(
101+
gestureActions = HAGesture.entries.associateWith { GestureAction.NONE },
102+
onSetAction = { _, _ -> },
103+
)
104+
}

0 commit comments

Comments
 (0)