From ee6408a5b0597250a4330e53f232df1190cea005 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 19 Dec 2021 12:16:16 +0000 Subject: [PATCH 1/2] Simpler scrolling --- .../com/surrus/peopleinspace/MainActivity.kt | 5 -- .../peopleinspace/PersonDetailsScreen.kt | 6 +- .../surrus/peopleinspace/PersonListScreen.kt | 21 +++-- .../peopleinspace/RotaryEventDispatcher.kt | 78 ++++++------------- 4 files changed, 43 insertions(+), 67 deletions(-) diff --git a/wearApp/src/main/java/com/surrus/peopleinspace/MainActivity.kt b/wearApp/src/main/java/com/surrus/peopleinspace/MainActivity.kt index 897cc03..89199d9 100644 --- a/wearApp/src/main/java/com/surrus/peopleinspace/MainActivity.kt +++ b/wearApp/src/main/java/com/surrus/peopleinspace/MainActivity.kt @@ -34,16 +34,11 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { - val rotaryEventDispatcher = RotaryEventDispatcher() - CompositionLocalProvider( LocalImageLoader provides imageLoader, - LocalRotaryEventDispatcher provides rotaryEventDispatcher, ) { val navController = rememberSwipeDismissableNavController() - RotaryEventHandlerSetup(rotaryEventDispatcher) - SwipeDismissableNavHost( navController = navController, startDestination = Screen.PersonList.route diff --git a/wearApp/src/main/java/com/surrus/peopleinspace/PersonDetailsScreen.kt b/wearApp/src/main/java/com/surrus/peopleinspace/PersonDetailsScreen.kt index 1f8df2d..2c3b5fb 100644 --- a/wearApp/src/main/java/com/surrus/peopleinspace/PersonDetailsScreen.kt +++ b/wearApp/src/main/java/com/surrus/peopleinspace/PersonDetailsScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -50,7 +51,6 @@ fun PersonDetailsScreen(personName: String) { } val scrollState = rememberScrollState() - RotaryEventState(scrollState) PersonDetailsScreen(person, scrollState) } @@ -60,6 +60,9 @@ private fun PersonDetailsScreen( person: Assignment?, scrollState: ScrollState = rememberScrollState(), ) { + // Activate scrolling + LocalView.current.requestFocus() + MaterialTheme { Scaffold( vignette = { @@ -71,6 +74,7 @@ private fun PersonDetailsScreen( ) { Column( modifier = Modifier + .scrollHandler(scrollState) .fillMaxSize() .padding(horizontal = if (LocalConfiguration.current.isScreenRound) 18.dp else 8.dp) .verticalScroll(scrollState), diff --git a/wearApp/src/main/java/com/surrus/peopleinspace/PersonListScreen.kt b/wearApp/src/main/java/com/surrus/peopleinspace/PersonListScreen.kt index 1005a2a..23dac8c 100644 --- a/wearApp/src/main/java/com/surrus/peopleinspace/PersonListScreen.kt +++ b/wearApp/src/main/java/com/surrus/peopleinspace/PersonListScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -23,8 +24,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.contentDescription @@ -55,9 +56,7 @@ fun PersonListScreen( peopleInSpaceViewModel: PeopleInSpaceViewModel = getViewModel() ) { val peopleState by peopleInSpaceViewModel.peopleInSpace.collectAsState() - val scrollState = rememberScalingLazyListState() - RotaryEventState(scrollState) - PersonListScreen(peopleState, personSelected, issMapClick, scrollState) + PersonListScreen(peopleState, personSelected, issMapClick) } @OptIn(ExperimentalAnimationApi::class) @@ -66,7 +65,6 @@ fun PersonListScreen( people: List?, personSelected: (person: Assignment) -> Unit, issMapClick: () -> Unit, - scrollState: ScalingLazyListState = rememberScalingLazyListState(), ) { MaterialTheme { AnimatedVisibility( @@ -75,7 +73,7 @@ fun PersonListScreen( ) { if (people != null) { if (people.isNotEmpty()) { - PersonList(people, personSelected, issMapClick, scrollState) + PersonList(people, personSelected, issMapClick) } else { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Card( @@ -98,15 +96,22 @@ fun PersonList( issMapClick: () -> Unit, scrollState: ScalingLazyListState = rememberScalingLazyListState(), ) { + // Activate scrolling + LocalView.current.requestFocus() + ScalingLazyColumn( + modifier = Modifier + .testTag(PersonListTag) + .scrollHandler(scrollState), contentPadding = PaddingValues(8.dp), - modifier = Modifier.testTag(PersonListTag), state = scrollState, ) { item { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { Button( - modifier = Modifier.size(ButtonDefaults.SmallButtonSize).wrapContentSize(), + modifier = Modifier + .size(ButtonDefaults.SmallButtonSize) + .wrapContentSize(), onClick = issMapClick ) { // https://www.svgrepo.com/svg/170716/international-space-station diff --git a/wearApp/src/main/java/com/surrus/peopleinspace/RotaryEventDispatcher.kt b/wearApp/src/main/java/com/surrus/peopleinspace/RotaryEventDispatcher.kt index 331d9ed..b71cb6d 100644 --- a/wearApp/src/main/java/com/surrus/peopleinspace/RotaryEventDispatcher.kt +++ b/wearApp/src/main/java/com/surrus/peopleinspace/RotaryEventDispatcher.kt @@ -1,75 +1,47 @@ package com.surrus.peopleinspace +import android.content.Context import android.view.MotionEvent import android.view.ViewConfiguration import androidx.compose.foundation.gestures.ScrollableState import androidx.compose.foundation.gestures.scrollBy -import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.input.pointer.RequestDisallowInterceptTouchEvent +import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalView import androidx.core.view.InputDeviceCompat import androidx.core.view.MotionEventCompat import androidx.core.view.ViewConfigurationCompat +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -val LocalRotaryEventDispatcher = staticCompositionLocalOf { - noLocalProvidedFor("LocalRotaryEventDispatcher") -} +fun processEvent(context: Context, scope: CoroutineScope, event: MotionEvent, scrollState: ScrollableState): Boolean { + if (event.action != MotionEvent.ACTION_SCROLL || + !event.isFromSource(InputDeviceCompat.SOURCE_ROTARY_ENCODER) + ) { + return false + } -/** - * Dispatcher to link rotary event to [ScrollableState]. - * The instance should be set up by calling [RotaryEventHandlerSetup] function. - */ -class RotaryEventDispatcher( - var scrollState: ScrollableState? = null -) { - suspend fun onRotate(delta: Float): Float? = - scrollState?.scrollBy(delta) -} - -/** - * Custom rotary event setup (Currently, Column / LazyColumn doesn't handle rotary event.) - * Refer to https://developer.android.com/training/wearables/user-input/rotary-input - */ -@Composable -fun RotaryEventHandlerSetup(rotaryEventDispatcher: RotaryEventDispatcher) { - val view = LocalView.current - val context = LocalContext.current - val scope = rememberCoroutineScope() - - view.requestFocus() - view.setOnGenericMotionListener { _, event -> - if (event?.action != MotionEvent.ACTION_SCROLL || - !event.isFromSource(InputDeviceCompat.SOURCE_ROTARY_ENCODER) - ) { - return@setOnGenericMotionListener false - } - - val delta = -event.getAxisValue(MotionEventCompat.AXIS_SCROLL) * + val delta = -event.getAxisValue(MotionEventCompat.AXIS_SCROLL) * ViewConfigurationCompat.getScaledVerticalScrollFactor( ViewConfiguration.get(context), context ) - scope.launch { - rotaryEventDispatcher.onRotate(delta) - } - true + scope.launch { + scrollState.scrollBy(delta) } + return true } -/** - * Register a [ScrollableState] to [LocalRotaryEventDispatcher] - */ -@Composable -fun RotaryEventState(scrollState: ScrollableState?) { - val dispatcher = LocalRotaryEventDispatcher.current - SideEffect { - dispatcher.scrollState = scrollState - } -} +@OptIn(ExperimentalComposeUiApi::class) +fun Modifier.scrollHandler(scrollState: ScrollableState): Modifier = composed { + val context = LocalContext.current + val scope = rememberCoroutineScope() -private fun noLocalProvidedFor(name: String): Nothing { - error("CompositionLocal $name not present") + this.pointerInteropFilter(RequestDisallowInterceptTouchEvent()) { event -> + println(event) + processEvent(context, scope, event, scrollState) + } } \ No newline at end of file From 4ab4ea18302f8c895d6ebe9c549c341d9b08051d Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 19 Dec 2021 12:24:28 +0000 Subject: [PATCH 2/2] Simpler scrolling --- .../peopleinspace/RotaryEventDispatcher.kt | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/wearApp/src/main/java/com/surrus/peopleinspace/RotaryEventDispatcher.kt b/wearApp/src/main/java/com/surrus/peopleinspace/RotaryEventDispatcher.kt index b71cb6d..30fc35d 100644 --- a/wearApp/src/main/java/com/surrus/peopleinspace/RotaryEventDispatcher.kt +++ b/wearApp/src/main/java/com/surrus/peopleinspace/RotaryEventDispatcher.kt @@ -1,10 +1,10 @@ package com.surrus.peopleinspace -import android.content.Context import android.view.MotionEvent import android.view.ViewConfiguration import androidx.compose.foundation.gestures.ScrollableState import androidx.compose.foundation.gestures.scrollBy +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -14,34 +14,27 @@ import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.platform.LocalContext import androidx.core.view.InputDeviceCompat import androidx.core.view.MotionEventCompat -import androidx.core.view.ViewConfigurationCompat -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -fun processEvent(context: Context, scope: CoroutineScope, event: MotionEvent, scrollState: ScrollableState): Boolean { - if (event.action != MotionEvent.ACTION_SCROLL || - !event.isFromSource(InputDeviceCompat.SOURCE_ROTARY_ENCODER) - ) { - return false - } - - val delta = -event.getAxisValue(MotionEventCompat.AXIS_SCROLL) * - ViewConfigurationCompat.getScaledVerticalScrollFactor( - ViewConfiguration.get(context), context - ) - scope.launch { - scrollState.scrollBy(delta) - } - return true -} - @OptIn(ExperimentalComposeUiApi::class) fun Modifier.scrollHandler(scrollState: ScrollableState): Modifier = composed { val context = LocalContext.current val scope = rememberCoroutineScope() + val scaledVerticalScrollFactor = + remember { ViewConfiguration.get(context).getScaledVerticalScrollFactor() } this.pointerInteropFilter(RequestDisallowInterceptTouchEvent()) { event -> - println(event) - processEvent(context, scope, event, scrollState) + if (event.action != MotionEvent.ACTION_SCROLL || + !event.isFromSource(InputDeviceCompat.SOURCE_ROTARY_ENCODER) + ) { + false + } else { + val delta = -event.getAxisValue(MotionEventCompat.AXIS_SCROLL) * + scaledVerticalScrollFactor + scope.launch { + scrollState.scrollBy(delta) + } + true + } } } \ No newline at end of file