feat: add tilt to swipe pages for home screen rearrangement and fix app drawer gap (#7500)
* feat and fix : Add Tilt to swipe pages setting Replace the multi-level Motion controls with a single Home screen toggle named “Tilt to swipe pages,” positioned below Infinite scrolling. Update the description to explain that tilting the phone moves between pages while rearranging apps or widgets, and make the pan controller use the toggle directly while retaining the existing preference key for compatibility. Fix for "[BUG] Large empty gap between app drawer search bar and app grid when app suggestions are disabled #7490" * EDITS : Coderabbit suggested edits for PR #7500 * Coderabbit suggested changes (2) #7500 * Update gradle-daemon-jvm.properties * Remove unrelated Gradle toolchain changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add docstrings for pan-to-move and all apps header changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add a tilt sensitivity control --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
1 parent
d180feb55f
commit
ce82ac1248
9 files changed
+299
-7
No files matched your search
@@ -34,7 +34,7 @@
|
||||
<dimen name="edit_mode_page_strip_tip_margin">8dp</dimen>
|
||||
|
||||
<!-- qsb -->
|
||||
<dimen name="all_apps_search_bar_bottom_padding">36dp</dimen>
|
||||
<dimen name="all_apps_search_bar_bottom_padding">0dp</dimen>
|
||||
<dimen name="all_apps_search_vertical_offset">30dp</dimen>
|
||||
<dimen name="qsb_g_icon_marginStart">16dp</dimen>
|
||||
<dimen name="qsb_icon_padding">10dp</dimen>
|
||||
|
||||
@@ -719,6 +719,10 @@
|
||||
<!-- General and home screen settings -->
|
||||
<string name="home_screen_rotation_label">Home screen rotation</string>
|
||||
<string name="home_screen_rotation_description">Allow home screen rotation when the device is rotated</string>
|
||||
<string name="pan_to_move_icon_label">Tilt to swipe pages</string>
|
||||
<string name="pan_to_move_icon_description">Tilt your phone left or right to move between pages during rearrangement of apps/widgets</string>
|
||||
<string name="pan_to_move_sensitivity_label">Tilt sensitivity</string>
|
||||
<string name="pan_to_move_sensor_unavailable">Pan to move is unavailable because no motion sensor could be registered.</string>
|
||||
|
||||
<string name="auto_updater_label">Automatically check for updates</string>
|
||||
<string name="auto_updater_description">Checks for Nightly updates when opening the About screen</string>
|
||||
|
||||
@@ -73,6 +73,15 @@ class PreferenceManager @Inject constructor(
|
||||
val iconPackPackage = StringPref("pref_iconPackPackage", "", reloadIcons)
|
||||
val themedIconPackPackage = StringPref("pref_themedIconPackPackage", "", reloadIcons)
|
||||
val allowRotation = BoolPref("pref_allowRotation", false)
|
||||
|
||||
/** Enables motion-driven interactions that use the device's motion sensors. */
|
||||
val motionEnabled = BoolPref("pref_motionEnabled", false)
|
||||
|
||||
/** Enables tilt-to-page gestures while rearranging home-screen items. */
|
||||
val panToMoveEnabled = BoolPref("pref_panToMoveEnabled", false)
|
||||
|
||||
/** Controls how far a motion reading must move toward the gesture threshold. */
|
||||
val panToMoveSensitivity = FloatPref("pref_panToMoveSensitivity", 0.5f)
|
||||
val wrapAdaptiveIcons = BoolPref("prefs_wrapAdaptive", true)
|
||||
val transparentIconBackground = BoolPref("prefs_transparentIconBackground", false)
|
||||
val shadowBGIcons = BoolPref("pref_shadowBGIcons", true)
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package app.lawnchair.ui.preferences.destinations
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorManager
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -59,6 +61,9 @@ object HomeScreenRoutes {
|
||||
const val POPUP_EDITOR = "popup_editor"
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows home-screen settings and exposes tilt-to-page only when a motion sensor is available.
|
||||
*/
|
||||
@Composable
|
||||
fun HomeScreenPreferences(
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -67,6 +72,15 @@ fun HomeScreenPreferences(
|
||||
val prefs2 = preferenceManager2()
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val hasMotionSensor = remember {
|
||||
context.getSystemService(SensorManager::class.java)
|
||||
?.let { manager ->
|
||||
manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null ||
|
||||
manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null
|
||||
} == true
|
||||
}
|
||||
val panToMoveEnabledAdapter = prefs.panToMoveEnabled.getAdapter()
|
||||
val panToMoveSensitivityAdapter = prefs.panToMoveSensitivity.getAdapter()
|
||||
PreferenceLayout(
|
||||
label = stringResource(id = R.string.home_screen_label),
|
||||
backArrowVisible = !LocalIsExpandedScreen.current,
|
||||
@@ -101,6 +115,24 @@ fun HomeScreenPreferences(
|
||||
description = stringResource(id = R.string.infinite_scrolling_description),
|
||||
)
|
||||
}
|
||||
if (hasMotionSensor) {
|
||||
PreferenceGroup {
|
||||
SwitchPreference(
|
||||
adapter = panToMoveEnabledAdapter,
|
||||
label = stringResource(id = R.string.pan_to_move_icon_label),
|
||||
description = stringResource(id = R.string.pan_to_move_icon_description),
|
||||
)
|
||||
ExpandAndShrink(visible = panToMoveEnabledAdapter.state.value) {
|
||||
SliderPreference(
|
||||
adapter = panToMoveSensitivityAdapter,
|
||||
label = stringResource(id = R.string.pan_to_move_sensitivity_label),
|
||||
valueRange = 0f..1f,
|
||||
step = 0.05f,
|
||||
showAsPercentage = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
PreferenceGroup(heading = stringResource(id = R.string.home_screen_actions)) {
|
||||
ClickablePreference(
|
||||
label = stringResource(id = R.string.remove_all_views_from_home_screen),
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2026, Lawnchair
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
package app.lawnchair.gestures
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import android.os.SystemClock
|
||||
import android.view.Surface
|
||||
import android.widget.Toast
|
||||
import com.android.launcher3.PagedView
|
||||
import com.android.launcher3.R
|
||||
import app.lawnchair.preferences.PreferenceManager
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Converts deliberate motion while dragging into one workspace page change at a time.
|
||||
*
|
||||
* Gyroscope input detects angular-velocity flicks, while accelerometer input calibrates a
|
||||
* drag-start tilt baseline and rearms after the device returns near that baseline.
|
||||
*/
|
||||
class PanToMoveController(
|
||||
private val context: Context,
|
||||
private val pager: PagedView<*>,
|
||||
) : SensorEventListener {
|
||||
|
||||
private val preferences = PreferenceManager.getInstance(context)
|
||||
private val sensorManager = context.getSystemService(SensorManager::class.java)
|
||||
private val gyroscope = sensorManager?.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
|
||||
private val accelerometer = sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
|
||||
private val sensor = gyroscope ?: accelerometer
|
||||
|
||||
private var filteredRate = 0f
|
||||
private var baselineTilt: Float? = null
|
||||
private var tiltTriggered = false
|
||||
private var lastPageChangeMs = 0L
|
||||
|
||||
/** Returns whether a motion sensor and manager are available for this controller. */
|
||||
val isSupported: Boolean
|
||||
get() = sensor != null && sensorManager != null
|
||||
|
||||
/**
|
||||
* Resets motion state and registers the preferred sensor for the duration of a drag.
|
||||
*
|
||||
* A gyroscope is preferred; devices without one use the accelerometer fallback and calibrate
|
||||
* its neutral tilt from the first reading.
|
||||
*/
|
||||
fun onDragStarted() {
|
||||
if (!preferences.panToMoveEnabled.get()) return
|
||||
val manager = sensorManager ?: run {
|
||||
Toast.makeText(context, R.string.pan_to_move_sensor_unavailable, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
filteredRate = 0f
|
||||
baselineTilt = null
|
||||
tiltTriggered = false
|
||||
lastPageChangeMs = 0L
|
||||
manager.unregisterListener(this)
|
||||
|
||||
val registered = gyroscope?.let {
|
||||
manager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME)
|
||||
} == true || accelerometer?.let {
|
||||
manager.registerListener(this, it, SensorManager.SENSOR_DELAY_GAME)
|
||||
} == true
|
||||
if (!registered) {
|
||||
Toast.makeText(context, R.string.pan_to_move_sensor_unavailable, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
/** Unregisters motion updates so sensor callbacks do not outlive the drag. */
|
||||
fun onDragEnded() {
|
||||
sensorManager?.unregisterListener(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a screen-relative motion reading and pages left or right after crossing a
|
||||
* sensitivity-adjusted threshold, enforcing a cooldown and accelerometer rearm.
|
||||
*/
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
if (!preferences.panToMoveEnabled.get()) {
|
||||
onDragEnded()
|
||||
return
|
||||
}
|
||||
|
||||
val isGyroscope = event.sensor.type == Sensor.TYPE_GYROSCOPE
|
||||
val sensitivity = preferences.panToMoveSensitivity.get().coerceIn(0f, 1f)
|
||||
val reading = screenRelativeReading(event, isGyroscope) ?: return
|
||||
if (isGyroscope) {
|
||||
// Gyroscope values are angular velocity. A flick is brief, so use a responsive
|
||||
// filter rather than smoothing it over several sensor frames.
|
||||
filteredRate = FILTER_WEIGHT * filteredRate + (1f - FILTER_WEIGHT) * reading
|
||||
} else {
|
||||
// Accelerometer fallback for devices without a gyroscope. Calibrate the neutral
|
||||
// position at drag start, then detect a deliberate change in tilt.
|
||||
val tilt = reading / SensorManager.GRAVITY_EARTH
|
||||
if (baselineTilt == null) {
|
||||
baselineTilt = tilt
|
||||
filteredRate = 0f
|
||||
return
|
||||
}
|
||||
val tiltDelta = tilt - baselineTilt!!
|
||||
if (abs(tiltDelta) < TILT_REARM_THRESHOLD) {
|
||||
tiltTriggered = false
|
||||
}
|
||||
filteredRate = FILTER_WEIGHT * filteredRate + (1f - FILTER_WEIGHT) * tiltDelta
|
||||
}
|
||||
val threshold = thresholdForSensitivity(sensitivity, isGyroscope)
|
||||
if (abs(filteredRate) < threshold) return
|
||||
if (!isGyroscope && tiltTriggered) return
|
||||
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
if (now - lastPageChangeMs < PAGE_CHANGE_COOLDOWN_MS) return
|
||||
|
||||
val moved = if (filteredRate > 0f) {
|
||||
pager.scrollLeft()
|
||||
} else {
|
||||
pager.scrollRight()
|
||||
}
|
||||
if (moved) {
|
||||
lastPageChangeMs = now
|
||||
filteredRate = 0f
|
||||
if (!isGyroscope) tiltTriggered = true
|
||||
}
|
||||
}
|
||||
|
||||
/** Accuracy changes do not alter the calibrated gesture state. */
|
||||
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
|
||||
|
||||
/**
|
||||
* Maps sensor axes into the current display orientation and returns the signed gesture axis.
|
||||
*/
|
||||
private fun screenRelativeReading(event: SensorEvent, isGyroscope: Boolean): Float? {
|
||||
val x = event.values[0]
|
||||
val y = event.values[1]
|
||||
return when (pager.display?.rotation ?: return null) {
|
||||
Surface.ROTATION_90 -> if (isGyroscope) -x else y
|
||||
Surface.ROTATION_180 -> if (isGyroscope) -y else -x
|
||||
Surface.ROTATION_270 -> if (isGyroscope) x else -y
|
||||
else -> if (isGyroscope) y else x
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val FILTER_WEIGHT = 0.35f
|
||||
const val MAX_RATE_THRESHOLD = 1.5f
|
||||
const val MIN_RATE_THRESHOLD = 0.35f
|
||||
const val MAX_TILT_THRESHOLD = 0.5f
|
||||
const val MIN_TILT_THRESHOLD = 0.2f
|
||||
const val TILT_REARM_THRESHOLD = 0.1f
|
||||
const val PAGE_CHANGE_COOLDOWN_MS = 700L
|
||||
}
|
||||
}
|
||||
|
||||
internal fun thresholdForSensitivity(sensitivity: Float, isGyroscope: Boolean): Float {
|
||||
val start = if (isGyroscope) 1.5f else 0.5f
|
||||
val end = if (isGyroscope) 0.35f else 0.2f
|
||||
return start + (end - start) * sensitivity.coerceIn(0f, 1f)
|
||||
}
|
||||
@@ -147,6 +147,7 @@ import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import app.lawnchair.hotseat.HotseatPagedView;
|
||||
import app.lawnchair.gestures.PanToMoveController;
|
||||
import app.lawnchair.preferences2.PreferenceCacheExtensionsKt;
|
||||
import static app.lawnchair.util.LawnchairUtilsKt.toBitmap;
|
||||
import app.lawnchair.LawnchairApp;
|
||||
@@ -338,6 +339,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
private DragController.DragListener mAccessibilityDragListener;
|
||||
|
||||
PreferenceManager2 mPreferenceManager2;
|
||||
private PanToMoveController mPanToMoveController;
|
||||
PreferenceManager mPreferenceManger;
|
||||
|
||||
/**
|
||||
@@ -364,6 +366,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
mWallpaperManager = WallpaperManager.getInstance(context);
|
||||
mAllAppsIconSize = mLauncher.getDeviceProfile().getAllAppsProfile().getIconSizePx();
|
||||
mPreferenceManager2 = PreferenceManager2.getInstance(context);
|
||||
mPanToMoveController = new PanToMoveController(context, this);
|
||||
mPreferenceManger = PreferenceManager.getInstance(context);
|
||||
mWallpaperOffset = new WallpaperOffsetInterpolator(this);
|
||||
|
||||
@@ -509,6 +512,10 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts workspace drag handling and motion sensing, except for accessibility drags confined
|
||||
* to a folder.
|
||||
*/
|
||||
@Override
|
||||
public void onDragStart(DragObject dragObject, DragOptions options) {
|
||||
if (ENFORCE_DRAG_EVENT_ORDER) {
|
||||
@@ -557,6 +564,9 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
if (mAccessibilityDragListener != null) {
|
||||
mAccessibilityDragListener.onDragStart(dragObject, options);
|
||||
}
|
||||
if (!(options.isAccessibleDrag && dragObject.dragSource instanceof Folder)) {
|
||||
mPanToMoveController.onDragStarted();
|
||||
}
|
||||
if (!mLauncher.isInState(EDIT_MODE)) {
|
||||
mLauncher.getStateManager().goToState(SPRING_LOADED);
|
||||
}
|
||||
@@ -573,6 +583,9 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
mDeferRemoveExtraEmptyScreen = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends workspace drag handling and unregisters motion sensors before clearing drag state.
|
||||
*/
|
||||
@Override
|
||||
public void onDragEnd() {
|
||||
if (ENFORCE_DRAG_EVENT_ORDER) {
|
||||
@@ -596,6 +609,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
if (mAccessibilityDragListener != null) {
|
||||
mAccessibilityDragListener.onDragEnd();
|
||||
}
|
||||
mPanToMoveController.onDragEnded();
|
||||
mDragInfo = null;
|
||||
mDragSourceInternal = null;
|
||||
}
|
||||
@@ -1672,8 +1686,12 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
mLauncher.getStateManager().addStateListener(mAccessibilityDropListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the workspace and ensures motion sensors are unregistered with the view.
|
||||
*/
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mPanToMoveController.onDragEnded();
|
||||
mWallpaperOffset.setWindowToken(null);
|
||||
mLauncher.getStateManager().removeStateListener(mAccessibilityDropListener);
|
||||
}
|
||||
@@ -2245,8 +2263,12 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
@Override
|
||||
public void prepareAccessibilityDrop() {}
|
||||
|
||||
/**
|
||||
* Stops motion sensing before processing the final drop on the workspace.
|
||||
*/
|
||||
@Override
|
||||
public void onDrop(final DragObject d, DragOptions options) {
|
||||
mPanToMoveController.onDragEnded();
|
||||
mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
|
||||
CellLayout dropTargetLayout = mDropToLayout;
|
||||
|
||||
@@ -2841,6 +2863,9 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
if (layout == null) {
|
||||
// Check visible pages
|
||||
IntSet visiblePageIndices = getVisiblePageIndices();
|
||||
if (isPageInTransition()) {
|
||||
visiblePageIndices.add(getNextPage());
|
||||
}
|
||||
for (int visiblePageIndex : visiblePageIndices) {
|
||||
layout = verifyInsidePage(visiblePageIndex, d.x, d.y);
|
||||
if (layout != null) break;
|
||||
|
||||
@@ -844,12 +844,14 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
|
||||
updateSearchResultsVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up header rows and hides the header shell when neither tabs nor visible row content
|
||||
* requires it, keeping adapter top padding consistent with that visibility.
|
||||
*/
|
||||
void setupHeader() {
|
||||
mAdditionalHeaderRows.forEach(row -> mHeader.onPluginDisconnected(row));
|
||||
|
||||
boolean hideSearchBar = isAppDrawerSearchBarHidden();
|
||||
// Keep personal/work tabs visible when search is off; only hide the empty header shell.
|
||||
mHeader.setVisibility((hideSearchBar && !mUsingTabs) ? View.GONE : View.VISIBLE);
|
||||
boolean tabsHidden = !mUsingTabs;
|
||||
mHeader.setup(
|
||||
mAH.get(AdapterHolder.MAIN).mRecyclerView,
|
||||
@@ -858,7 +860,11 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
|
||||
getCurrentPage(),
|
||||
tabsHidden);
|
||||
|
||||
int padding = (hideSearchBar && !mUsingTabs) ? 0 : mHeader.getMaxTranslation();
|
||||
// Keep personal/work tabs or visible rows active; hide the empty header shell.
|
||||
boolean hasHeaderContent = mUsingTabs || (!hideSearchBar && mHeader.hasVisibleContent());
|
||||
mHeader.setVisibility(hasHeaderContent ? View.VISIBLE : View.GONE);
|
||||
|
||||
int padding = hasHeaderContent ? mHeader.getMaxTranslation() : 0;
|
||||
mAH.forEach(adapterHolder -> {
|
||||
adapterHolder.mPadding.top = padding;
|
||||
adapterHolder.applyPadding();
|
||||
@@ -1442,6 +1448,10 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows search results without the header, or restores the header only when tabs or visible
|
||||
* header rows provide content.
|
||||
*/
|
||||
protected void updateSearchResultsVisibility() {
|
||||
if (isSearching()) {
|
||||
getSearchRecyclerView().setVisibility(VISIBLE);
|
||||
@@ -1450,9 +1460,10 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
|
||||
} else {
|
||||
getSearchRecyclerView().setVisibility(GONE);
|
||||
getAppsRecyclerViewContainer().setVisibility(VISIBLE);
|
||||
// Keep the empty header shell hidden when search is off and there are no tabs.
|
||||
mHeader.setVisibility(
|
||||
(isAppDrawerSearchBarHidden() && !mUsingTabs) ? View.GONE : View.VISIBLE);
|
||||
// Keep personal/work tabs or visible rows active; hide the empty header shell.
|
||||
boolean hasHeaderContent = mUsingTabs
|
||||
|| (!isAppDrawerSearchBarHidden() && mHeader.hasVisibleContent());
|
||||
mHeader.setVisibility(hasHeaderContent ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
if (mHeader.isSetUp()) {
|
||||
mHeader.setActiveRV(getCurrentPage());
|
||||
|
||||
@@ -243,6 +243,18 @@ public class FloatingHeaderView extends LinearLayout implements
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether any header row both has content and is currently visible.
|
||||
*/
|
||||
public boolean hasVisibleContent() {
|
||||
for (FloatingHeaderRow row : mAllRows) {
|
||||
if (row.hasVisibleContent() && row.shouldDraw()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setup(AllAppsRecyclerView mainRV, AllAppsRecyclerView workRV, SearchRecyclerView searchRV,
|
||||
int activeRV, boolean tabsHidden) {
|
||||
for (FloatingHeaderRow row : mAllRows) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2026, Lawnchair
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
package app.lawnchair.gestures
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class PanToMoveControllerTest {
|
||||
|
||||
@Test
|
||||
fun tiltThreshold_decreasesAsSensitivityIncreases() {
|
||||
assertThat(thresholdForSensitivity(0f, isGyroscope = false)).isEqualTo(0.5f)
|
||||
assertThat(thresholdForSensitivity(0.5f, isGyroscope = false)).isEqualTo(0.35f)
|
||||
assertThat(thresholdForSensitivity(1f, isGyroscope = false)).isEqualTo(0.2f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tiltThreshold_clampsSensitivityToSupportedRange() {
|
||||
assertThat(thresholdForSensitivity(-1f, isGyroscope = false)).isEqualTo(0.5f)
|
||||
assertThat(thresholdForSensitivity(2f, isGyroscope = false)).isEqualTo(0.2f)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user