Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04e78205dd | ||
|
|
cef6929a41 | ||
|
|
fba65e091f |
No files matched your search
@@ -211,6 +211,13 @@ if ($NoSmokeTest) {
|
||||
if ($LASTEXITCODE -ne 0 -or -not ($install -match 'Success')) {
|
||||
throw "Could not install the APK on $emulator. If an older build signed with another key is installed there, uninstall it first: adb uninstall $AppId"
|
||||
}
|
||||
# When this app is the emulator's home app, Android restarts it the moment the old copy is
|
||||
# gone, and that first start can die inside Android itself while the files are still being
|
||||
# swapped (a NullPointerException in ActivityThread.handleBindApplication, with no line of
|
||||
# this app in it). That is not the new build crashing: let the swap settle, then start
|
||||
# from a clean slate and judge what happens from there.
|
||||
Start-Sleep -Seconds 6
|
||||
& $Adb -s $emulator shell am force-stop $AppId
|
||||
& $Adb -s $emulator logcat -b crash -c
|
||||
& $Adb -s $emulator shell am start -n $HomeActivity | Out-Null
|
||||
Write-Host "Installed on $emulator and started; watching for $SmokeTestSeconds seconds..."
|
||||
|
||||
@@ -92,6 +92,12 @@
|
||||
<string name="flexera_scrub_from_home">Scrub from the home screen</string>
|
||||
<string name="flexera_scrub_from_home_description">Slide up or down along the alphabet\'s edge of the home screen to open the app list at a letter. Swipe up and hold does the same from anywhere.</string>
|
||||
|
||||
<!-- Quick launch: a swipe on past the first or the last home screen page (app.flexera.home.QuickLaunch) -->
|
||||
<string name="flexera_quick_launch">Quick launch</string>
|
||||
<string name="flexera_quick_launch_description">Swipe on from the first or the last home screen page, as if there were one more, to open an app or do something else. Has no effect while Infinite scrolling is on.</string>
|
||||
<string name="flexera_quick_launch_first_page">Swipe past the first page</string>
|
||||
<string name="flexera_quick_launch_last_page">Swipe past the last page</string>
|
||||
|
||||
<!-- The picture widget that comes with the launcher (app.flexera.widget) -->
|
||||
<!-- Its name in the list of widgets -->
|
||||
<string name="flexera_picture_widget">Pictures</string>
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package app.flexera.home
|
||||
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Context
|
||||
import android.content.pm.LauncherApps
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import app.lawnchair.LawnchairLauncher
|
||||
import app.lawnchair.gestures.config.GestureHandlerConfig
|
||||
import app.lawnchair.gestures.handlers.OpenAppTarget
|
||||
import app.lawnchair.preferences2.PreferenceManager2
|
||||
import app.lawnchair.preferences2.firstCached
|
||||
import com.android.launcher3.Launcher
|
||||
import com.android.launcher3.LauncherState
|
||||
import com.android.launcher3.util.EdgeEffectCompat
|
||||
|
||||
/**
|
||||
* Quick launch: a swipe on from the first home screen page, towards the page that is not there,
|
||||
* opens an app the owner has chosen (or does another of the launcher's gesture actions); the
|
||||
* same past the last page.
|
||||
*
|
||||
* The home screen already answers such a swipe by stretching and springing back. That stretch is
|
||||
* an edge effect, one per side, and quick launch is an edge effect that also keeps count of how
|
||||
* far the finger has pulled: far enough, or a flick, and lifting the finger does the action.
|
||||
* Launcher3 uses the same place for the Google feed on other phones (OverlayEdgeEffect).
|
||||
*/
|
||||
object QuickLaunch {
|
||||
|
||||
private const val TAG = "QuickLaunch"
|
||||
|
||||
/** How far the finger has to pull past the page, as a share of the screen's width. */
|
||||
private const val ARM_DISTANCE = 0.14f
|
||||
|
||||
/** A flick counts from this much pull, at [FLICK_DP_PER_SECOND] or faster. */
|
||||
private const val FLICK_DISTANCE = 0.02f
|
||||
private const val FLICK_DP_PER_SECOND = 600f
|
||||
|
||||
/**
|
||||
* The edge effect for one side of the home screen pages. [atLeftEdge] says which side of
|
||||
* the screen; which page that is the end of depends on the reading direction ([rtl]).
|
||||
*/
|
||||
@JvmStatic
|
||||
fun edgeEffect(workspace: View, launcher: Launcher, atLeftEdge: Boolean, rtl: Boolean): EdgeEffectCompat =
|
||||
Effect(workspace, launcher, firstPage = atLeftEdge != rtl, atLeftEdge = atLeftEdge)
|
||||
|
||||
/** What the owner has chosen for a swipe past the first or the last page. */
|
||||
private fun choice(context: Context, firstPage: Boolean): GestureHandlerConfig {
|
||||
val prefs = PreferenceManager2.getInstance(context)
|
||||
return if (firstPage) {
|
||||
prefs.firstPageSwipeGestureHandler.firstCached()
|
||||
} else {
|
||||
prefs.lastPageSwipeGestureHandler.firstCached()
|
||||
}
|
||||
}
|
||||
|
||||
private fun launch(launcher: Launcher, firstPage: Boolean, fromLeft: Boolean) {
|
||||
val choice = choice(launcher, firstPage)
|
||||
if (choice is GestureHandlerConfig.NoOp) return
|
||||
val target = (choice as? GestureHandlerConfig.OpenApp)?.target
|
||||
if (target is OpenAppTarget.App) {
|
||||
// The app opens out of the screen edge the finger pulled from, as near to a page
|
||||
// sliding in as Android lets an ordinary app come: it plays a sideways slide of
|
||||
// the launcher's own making only for the phone's built-in launcher.
|
||||
val root = launcher.rootView
|
||||
val options = ActivityOptions.makeClipRevealAnimation(
|
||||
root,
|
||||
if (fromLeft) 0 else root.width - 1,
|
||||
0,
|
||||
1,
|
||||
root.height,
|
||||
)
|
||||
try {
|
||||
launcher.getSystemService(LauncherApps::class.java)
|
||||
?.startMainActivity(target.key.componentName, target.key.user, null, options.toBundle())
|
||||
} catch (e: RuntimeException) {
|
||||
// The chosen app is gone or cannot be started; nothing to open.
|
||||
Log.w(TAG, "The quick launch app could not be opened", e)
|
||||
}
|
||||
return
|
||||
}
|
||||
(launcher as? LawnchairLauncher)?.gestureController?.handle(choice)
|
||||
}
|
||||
|
||||
private class Effect(
|
||||
private val workspace: View,
|
||||
private val launcher: Launcher,
|
||||
private val firstPage: Boolean,
|
||||
private val atLeftEdge: Boolean,
|
||||
) : EdgeEffectCompat(workspace.context) {
|
||||
|
||||
/** How far the finger has pulled in this touch, as a share of the screen's width. */
|
||||
private var pulled = 0f
|
||||
|
||||
/** Whether there is anything to do on this side; looked up when a pull begins. */
|
||||
private var active = false
|
||||
private var armed = false
|
||||
private var flicked = false
|
||||
|
||||
private val flickVelocity = FLICK_DP_PER_SECOND * workspace.resources.displayMetrics.density
|
||||
|
||||
override fun onPullDistance(deltaDistance: Float, displacement: Float, ev: MotionEvent): Float {
|
||||
val consumed = super.onPullDistance(deltaDistance, displacement, ev)
|
||||
if (pulled == 0f && deltaDistance > 0f) {
|
||||
active = choice(workspace.context, firstPage) !is GestureHandlerConfig.NoOp &&
|
||||
launcher.isInState(LauncherState.NORMAL) &&
|
||||
!launcher.dragController.isDragging
|
||||
}
|
||||
pulled = (pulled + deltaDistance).coerceAtLeast(0f)
|
||||
val nowArmed = active && pulled >= ARM_DISTANCE
|
||||
if (nowArmed != armed) {
|
||||
armed = nowArmed
|
||||
// A tick when the pull is far enough, and again when it is taken back.
|
||||
tick()
|
||||
}
|
||||
return consumed
|
||||
}
|
||||
|
||||
override fun onFlingVelocity(velocity: Int) {
|
||||
// A pull at the left side is a finger moving right, which is a positive speed.
|
||||
val outward = if (atLeftEdge) velocity else -velocity
|
||||
flicked = active && pulled >= FLICK_DISTANCE && outward >= flickVelocity
|
||||
}
|
||||
|
||||
override fun onRelease(ev: MotionEvent) {
|
||||
val lifted = ev.actionMasked == MotionEvent.ACTION_UP
|
||||
val fire = lifted && active && (armed || flicked)
|
||||
val tickNow = fire && !armed
|
||||
reset()
|
||||
super.onRelease(ev)
|
||||
if (fire) {
|
||||
if (tickNow) tick()
|
||||
// After the page has finished handling this touch.
|
||||
workspace.post { launch(launcher, firstPage, fromLeft = atLeftEdge) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRelease() {
|
||||
reset()
|
||||
super.onRelease()
|
||||
}
|
||||
|
||||
override fun finish() {
|
||||
reset()
|
||||
super.finish()
|
||||
}
|
||||
|
||||
private fun reset() {
|
||||
pulled = 0f
|
||||
active = false
|
||||
armed = false
|
||||
flicked = false
|
||||
}
|
||||
|
||||
private fun tick() {
|
||||
val feedback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
|
||||
} else {
|
||||
HapticFeedbackConstants.CLOCK_TICK
|
||||
}
|
||||
// The pages themselves have haptics switched off; the window's root has not.
|
||||
launcher.rootView.performHapticFeedback(feedback)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,4 @@
|
||||
# build.gradle reads this and derives the Android version code from it
|
||||
# (major * 10000 + minor * 100 + patch), so the name and the code can never disagree.
|
||||
# flexera/release.ps1 raises it for every release.
|
||||
versionName=0.8.1
|
||||
versionName=0.9.0
|
||||
@@ -874,6 +874,18 @@ class PreferenceManager2 @Inject constructor(
|
||||
defaultValue = GestureHandlerConfig.NoOp,
|
||||
)
|
||||
|
||||
// Flexera Launcher: quick launch. What a swipe on from the first home screen page, towards
|
||||
// the page that is not there, does; and the same past the last page (app.flexera.home.QuickLaunch).
|
||||
val firstPageSwipeGestureHandler = serializablePreference<GestureHandlerConfig>(
|
||||
key = stringPreferencesKey("flexera_first_page_swipe_gesture_handler"),
|
||||
defaultValue = GestureHandlerConfig.NoOp,
|
||||
)
|
||||
|
||||
val lastPageSwipeGestureHandler = serializablePreference<GestureHandlerConfig>(
|
||||
key = stringPreferencesKey("flexera_last_page_swipe_gesture_handler"),
|
||||
defaultValue = GestureHandlerConfig.NoOp,
|
||||
)
|
||||
|
||||
val autoUpdaterNightly = preference(
|
||||
key = booleanPreferencesKey(name = "enable_nightly_auto_updater"),
|
||||
defaultValue = if (BuildConfig.APPLICATION_ID.contains("nightly")) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.lawnchair.ui.preferences.destinations
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -53,6 +54,23 @@ fun GesturePreferences(
|
||||
label = stringResource(id = R.string.gesture_back_tap),
|
||||
)
|
||||
}
|
||||
// Flexera Launcher: quick launch, a swipe on past the first or the last home screen page
|
||||
// (app.flexera.home.QuickLaunch). It rides on Android's own page stretch, new in Android 12.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PreferenceGroup(
|
||||
heading = stringResource(id = R.string.flexera_quick_launch),
|
||||
description = stringResource(id = R.string.flexera_quick_launch_description),
|
||||
) {
|
||||
GestureHandlerPreference(
|
||||
adapter = prefs.firstPageSwipeGestureHandler.getAdapter(),
|
||||
label = stringResource(id = R.string.flexera_quick_launch_first_page),
|
||||
)
|
||||
GestureHandlerPreference(
|
||||
adapter = prefs.lastPageSwipeGestureHandler.getAdapter(),
|
||||
label = stringResource(id = R.string.flexera_quick_launch_last_page),
|
||||
)
|
||||
}
|
||||
}
|
||||
PreferenceGroup(heading = stringResource(id = R.string.sleep_mode_label)) {
|
||||
ListPreference(
|
||||
adapter = prefs.sleepMode.getAdapter(),
|
||||
|
||||
@@ -372,6 +372,13 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
|
||||
setHapticFeedbackEnabled(false);
|
||||
initWorkspace();
|
||||
// Flexera Launcher: quick launch. The stretch at either end of the pages doubles as a
|
||||
// gesture that opens an app the owner has chosen (app.flexera.home.QuickLaunch). From
|
||||
// Android 12, where that stretch is Android's own; before, Lawnchair draws one itself.
|
||||
if (Utilities.ATLEAST_S) {
|
||||
mEdgeGlowLeft = app.flexera.home.QuickLaunch.edgeEffect(this, mLauncher, true, mIsRtl);
|
||||
mEdgeGlowRight = app.flexera.home.QuickLaunch.edgeEffect(this, mLauncher, false, mIsRtl);
|
||||
}
|
||||
|
||||
// Disable multitouch across the workspace/all apps/customize tray
|
||||
setMotionEventSplittingEnabled(true);
|
||||
@@ -1476,7 +1483,10 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
public void setLauncherOverlay(LauncherOverlayTouchProxy overlay) {
|
||||
final EdgeEffectCompat newEffect;
|
||||
if (overlay == null) {
|
||||
newEffect = new EdgeEffectCompat(getContext());
|
||||
// Flexera Launcher: without a feed, the first page's end is quick launch again.
|
||||
newEffect = Utilities.ATLEAST_S
|
||||
? app.flexera.home.QuickLaunch.edgeEffect(this, mLauncher, !mIsRtl, mIsRtl)
|
||||
: new EdgeEffectCompat(getContext());
|
||||
mOverlayEdgeEffect = null;
|
||||
} else {
|
||||
newEffect = mOverlayEdgeEffect = new OverlayEdgeEffect(getContext(), overlay);
|
||||
|
||||
Reference in new issue
Block a user