Add scrub from home, fix the work profile layout, and let swipe up and hold work with three buttons
Scrub from home (PRD AD-10) - New setting, off by default: a vertical slide along the alphabet's edge of the home screen opens the drawer and scrubs it in the same touch (FlexeraEdgeScrubController). It claims a touch only once it has clearly moved up or down, and never during an item drag. Nothing is drawn on the home screen. Work profile (PRD AD-14) - The Personal and Work tabs and the work info card were one icon wide, because their width came from grid maths that assumes several columns. In the one-column list they now line up with the search bar. - The info card's own scroller section no longer shows up as a letter in the alphabet. Navigation modes (PRD SI-2, AD-17) - With three-button navigation, the controller that carries swipe up and hold stood aside completely, so a hold did nothing there. It now takes the ordinary swipe up from the home screen in that mode too. Wave alphabet - The list darkens under the wave where the letters cross icons and names: always from the left edge, and from the right edge in proportion to how far the wave reaches in. Release script - -BuildOnly builds and checks the APK without publishing, so a version can be tried first and then published with -SkipBuild. Checked on the Android 17 emulator (debug build): work tabs at full width, scrub from the home screen's edge, the search field keeping its background while typing. The three-button change and the right-edge darkening were written after that build and are not yet checked. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
1 parent
cdb076bba5
commit
c9744a1994
10 files changed
+197
-21
No files matched your search
@@ -26,6 +26,10 @@
|
||||
.PARAMETER NotesFile
|
||||
A text file with the notes, as an alternative to -Notes.
|
||||
|
||||
.PARAMETER BuildOnly
|
||||
Stop after building and checking the APK, without publishing. Try the APK on a phone, then
|
||||
run the script again with -SkipBuild to publish that same file.
|
||||
|
||||
.PARAMETER SkipBuild
|
||||
Publish the APK that is already in build\outputs instead of building again.
|
||||
|
||||
@@ -37,11 +41,16 @@
|
||||
|
||||
.EXAMPLE
|
||||
.\flexera\release.ps1 -Version 0.2.0 -Notes "The alphabet follows the finger more closely."
|
||||
|
||||
.EXAMPLE
|
||||
.\flexera\release.ps1 -Version 0.2.0 -BuildOnly
|
||||
.\flexera\release.ps1 -Version 0.2.0 -SkipBuild -Notes "The alphabet follows the finger more closely."
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Version,
|
||||
[string]$Notes = "",
|
||||
[string]$NotesFile,
|
||||
[switch]$BuildOnly,
|
||||
[switch]$SkipBuild,
|
||||
[switch]$AllowDirty,
|
||||
[switch]$PushSource
|
||||
@@ -163,6 +172,12 @@ if ($signer -match 'Android Debug') { throw "The APK is signed with the debug ke
|
||||
Write-Host $signer
|
||||
Write-Host ("APK: {0} ({1:N1} MB)" -f (Split-Path $apk -Leaf), ((Get-Item $apk).Length / 1MB))
|
||||
|
||||
if ($BuildOnly) {
|
||||
Write-Host "`nBuilt and checked, not published: $apk" -ForegroundColor Green
|
||||
Write-Host "Publish this file with: .\flexera\release.ps1 -Version $Version -SkipBuild -Notes `"...`""
|
||||
return
|
||||
}
|
||||
|
||||
# --- 4. Release ----------------------------------------------------------------------------------
|
||||
|
||||
Step "4/6 Creating release v$Version in $ReleaseRepository"
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
<string name="flexera_alphabet_heading">Alphabet</string>
|
||||
<string name="flexera_alphabet_on_left">Alphabet on the left edge</string>
|
||||
<string name="flexera_alphabet_on_left_description">For left-handed use. The letters normally sit on the right edge.</string>
|
||||
<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>
|
||||
|
||||
<!-- Updates section of the About screen (app.flexera.update.FlexeraUpdater) -->
|
||||
<string name="flexera_update_heading">Updates</string>
|
||||
|
||||
@@ -2,6 +2,7 @@ package app.flexera.drawer
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import android.util.TypedValue
|
||||
@@ -198,13 +199,43 @@ object FlexeraDrawer {
|
||||
ev: MotionEvent,
|
||||
) {
|
||||
val wave = waveAlphabet(appsView) ?: return
|
||||
val bounds = Rect()
|
||||
dragLayer.getDescendantRectRelativeToSelf(wave, bounds)
|
||||
val x = ev.x - bounds.left
|
||||
val y = ev.y - bounds.top
|
||||
val offset = layoutOffset(wave, dragLayer)
|
||||
val x = ev.x - offset.x
|
||||
val y = ev.y - offset.y
|
||||
if (wave.isScrubbing) wave.moveScrub(x, y) else wave.beginScrub(x, y)
|
||||
}
|
||||
|
||||
/**
|
||||
* True when [ev], given in the drag layer's coordinates, lands where the wave alphabet's
|
||||
* strip will be once the drawer is open. Scrub from home (PRD AD-10) starts there.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun isOnHomeEdge(
|
||||
appsView: ActivityAllAppsContainerView<*>,
|
||||
dragLayer: BaseDragLayer<*>,
|
||||
ev: MotionEvent,
|
||||
): Boolean {
|
||||
val wave = waveAlphabet(appsView) ?: return false
|
||||
val offset = layoutOffset(wave, dragLayer)
|
||||
return wave.isOnStripBand(ev.x - offset.x, ev.y - offset.y)
|
||||
}
|
||||
|
||||
/**
|
||||
* Where [view] sits inside [ancestor] going by layout alone. A closed drawer is parked
|
||||
* off-screen with a translation; this ignores it, giving the place the view has when the
|
||||
* drawer is open.
|
||||
*/
|
||||
private fun layoutOffset(view: View, ancestor: View): Point {
|
||||
val offset = Point()
|
||||
var current: View? = view
|
||||
while (current != null && current !== ancestor) {
|
||||
offset.x += current.left
|
||||
offset.y += current.top
|
||||
current = current.parent as? View
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
/** Ends a scrub started by [scrubFromSwipe]; the drawer stays open at the chosen letter. */
|
||||
@JvmStatic
|
||||
fun endScrubFromSwipe(appsView: ActivityAllAppsContainerView<*>) {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package app.flexera.drawer
|
||||
|
||||
import android.view.MotionEvent
|
||||
import android.view.ViewConfiguration
|
||||
import app.lawnchair.preferences.PreferenceManager
|
||||
import com.android.launcher3.AbstractFloatingView
|
||||
import com.android.launcher3.Launcher
|
||||
import com.android.launcher3.LauncherState
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.util.TouchController
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Scrub from home (PRD AD-10). With the setting on, a vertical slide that starts on the home
|
||||
* screen's edge, where the drawer's alphabet sits, opens the drawer and scrubs it in the same
|
||||
* touch. Nothing is drawn on the home screen; the letters appear with the drawer.
|
||||
*
|
||||
* The edge is shared with page swipes, taps and item drags, so this claims a touch only once it
|
||||
* has clearly moved up or down, and never while an item is being dragged. It is off by default.
|
||||
*/
|
||||
class FlexeraEdgeScrubController(private val launcher: Launcher) : TouchController {
|
||||
|
||||
private val touchSlop = ViewConfiguration.get(launcher).scaledTouchSlop
|
||||
private val prefs = PreferenceManager.getInstance(launcher)
|
||||
|
||||
/** The current touch started on the edge and may still turn into a scrub. */
|
||||
private var candidate = false
|
||||
private var scrubbing = false
|
||||
private var downX = 0f
|
||||
private var downY = 0f
|
||||
|
||||
override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
when (ev.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
scrubbing = false
|
||||
candidate = canStart(ev)
|
||||
downX = ev.x
|
||||
downY = ev.y
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> if (candidate) {
|
||||
val dx = abs(ev.x - downX)
|
||||
val dy = abs(ev.y - downY)
|
||||
when {
|
||||
// A long press picked something up, or the finger went sideways: not ours.
|
||||
launcher.dragController.isDragging || (dx > touchSlop && dx >= dy) -> candidate = false
|
||||
|
||||
dy > touchSlop && dy > dx * VERTICAL_BIAS -> {
|
||||
candidate = false
|
||||
scrubbing = true
|
||||
launcher.stateManager.goToState(LauncherState.ALL_APPS)
|
||||
FlexeraDrawer.scrubFromSwipe(launcher.appsView, launcher.dragLayer, ev)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> candidate = false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onControllerTouchEvent(ev: MotionEvent): Boolean {
|
||||
if (!scrubbing) return false
|
||||
when (ev.actionMasked) {
|
||||
MotionEvent.ACTION_MOVE ->
|
||||
FlexeraDrawer.scrubFromSwipe(launcher.appsView, launcher.dragLayer, ev)
|
||||
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
scrubbing = false
|
||||
FlexeraDrawer.endScrubFromSwipe(launcher.appsView)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun canStart(ev: MotionEvent): Boolean =
|
||||
prefs.flexeraScrubFromHome.get() &&
|
||||
(ev.edgeFlags and Utilities.EDGE_NAV_BAR) == 0 &&
|
||||
launcher.isInState(LauncherState.NORMAL) &&
|
||||
AbstractFloatingView.getTopOpenView(launcher) == null &&
|
||||
FlexeraDrawer.isOnHomeEdge(launcher.appsView, launcher.dragLayer, ev)
|
||||
|
||||
override fun dump(): String = "FlexeraEdgeScrubController candidate=$candidate scrubbing=$scrubbing"
|
||||
|
||||
private companion object {
|
||||
/** A slide counts as vertical when it has moved this many times further up or down than sideways. */
|
||||
const val VERTICAL_BIAS = 2f
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,9 @@ class WaveAlphabetView(
|
||||
private val waveWidth = res.getDimension(R.dimen.flexera_wave_width)
|
||||
private val letterSize = res.getDimension(R.dimen.flexera_wave_letter_size)
|
||||
|
||||
/** The section name Launcher3 gives the work profile's info card, an "i" in a circle. */
|
||||
private val workInfoSection = res.getString(R.string.work_profile_edu_section)
|
||||
|
||||
// The drawer has no background, so the letters are white like the app names: slightly
|
||||
// dimmed at rest, full white near the finger.
|
||||
private val idleColor = ColorUtils.setAlphaComponent(Color.WHITE, 0xB3)
|
||||
@@ -129,8 +132,9 @@ class WaveAlphabetView(
|
||||
for (section in sections) {
|
||||
val label = section.sectionName?.toString().orEmpty()
|
||||
// Launcher3 repeats the last section once more so its own scroller can reach the
|
||||
// bottom, and Private Space uses a long badge string; neither is a letter.
|
||||
if (label.isEmpty() || label.length > 2) continue
|
||||
// bottom, Private Space uses a long badge string, and the work profile's info card
|
||||
// has a section of its own; none of these is a letter.
|
||||
if (label.isEmpty() || label.length > 2 || label == workInfoSection) continue
|
||||
if (result.isNotEmpty() && result.last().label == label) continue
|
||||
result.add(Letter(label, section.position))
|
||||
}
|
||||
@@ -218,19 +222,24 @@ class WaveAlphabetView(
|
||||
if (layoutDirty) computeLayout()
|
||||
if (letters.isEmpty()) return
|
||||
|
||||
// On the left edge the wave reaches out over the icons and names, so darken the list
|
||||
// under it for as long as the finger is down. On the right it crosses empty space.
|
||||
if (onLeft && press > 0f) {
|
||||
canvas.drawColor(Color.argb((LEFT_SCRUB_DIM * press * 255).toInt(), 0, 0, 0))
|
||||
}
|
||||
|
||||
// Where the letters rest, and which way "away from the edge" points.
|
||||
val baseX = if (onLeft) edgeInset else width - edgeInset
|
||||
val inward = if (onLeft) 1f else -1f
|
||||
// The wave's peak sits a fixed gap before the finger, so it reaches further out the
|
||||
// further the finger is from the edge.
|
||||
val fingerDistance = (fingerX - baseX) * inward
|
||||
val reach = (fingerDistance + fingerGap).coerceIn(minReach, width * 0.7f) * press
|
||||
val maxReach = width * 0.7f
|
||||
val fullReach = (fingerDistance + fingerGap).coerceIn(minReach, maxReach)
|
||||
val reach = fullReach * press
|
||||
|
||||
// Darken the list under the wave where its letters cross icons and names. From the
|
||||
// right edge that only happens once the wave reaches well into the screen, as it does
|
||||
// after swipe up and hold; from the left edge the icons sit right beside the strip.
|
||||
if (press > 0f) {
|
||||
val overList = if (onLeft) 1f else ((fullReach - minReach) / (maxReach - minReach)).coerceIn(0f, 1f)
|
||||
val dim = (SCRUB_DIM * press * overList * 255).toInt()
|
||||
if (dim > 0) canvas.drawColor(Color.argb(dim, 0, 0, 0))
|
||||
}
|
||||
|
||||
for (i in letters.indices) {
|
||||
val y = letterYs[i]
|
||||
@@ -258,9 +267,16 @@ class WaveAlphabetView(
|
||||
// ---- Touch -------------------------------------------------------------------------------
|
||||
|
||||
/** True when a touch at this point, in this view's coordinates, starts a scrub. */
|
||||
fun isOnStrip(x: Float, y: Float): Boolean {
|
||||
fun isOnStrip(x: Float, y: Float): Boolean =
|
||||
appsView.isInAllApps && !appsView.isSearching && isOnStripBand(x, y)
|
||||
|
||||
/**
|
||||
* True when this point lies on the band along the edge that the letters occupy, whether or
|
||||
* not the drawer is open. Scrub from home (PRD AD-10) asks this while the drawer is closed.
|
||||
*/
|
||||
fun isOnStripBand(x: Float, y: Float): Boolean {
|
||||
if (layoutDirty) computeLayout()
|
||||
if (letters.isEmpty() || !appsView.isInAllApps || appsView.isSearching) return false
|
||||
if (letters.isEmpty()) return false
|
||||
val onEdge = if (onLeft) x <= touchWidth else x >= width - touchWidth
|
||||
return onEdge && y >= stripTop - verticalMargin && y <= stripBottom + verticalMargin
|
||||
}
|
||||
@@ -449,8 +465,8 @@ class WaveAlphabetView(
|
||||
/** How much its neighbours grow, tapering with distance from the finger. */
|
||||
const val NEAR_GROWTH = 0.5f
|
||||
|
||||
/** How dark the list gets under a scrub from the left edge (0 = unchanged, 1 = black). */
|
||||
const val LEFT_SCRUB_DIM = 0.55f
|
||||
/** How dark the list gets under a wave that lies fully over it (0 = unchanged, 1 = black). */
|
||||
const val SCRUB_DIM = 0.55f
|
||||
|
||||
/** The tallest a resting letter may be, as a share of the vertical room it has. */
|
||||
const val MAX_LETTER_SHARE_OF_SLOT = 0.85f
|
||||
|
||||
@@ -61,6 +61,11 @@ fun FlexeraDrawerSettings(modifier: Modifier = Modifier) {
|
||||
description = stringResource(id = R.string.flexera_alphabet_on_left_description),
|
||||
adapter = prefs.flexeraAlphabetOnLeft.getAdapter(),
|
||||
)
|
||||
SwitchPreference(
|
||||
label = stringResource(id = R.string.flexera_scrub_from_home),
|
||||
description = stringResource(id = R.string.flexera_scrub_from_home_description),
|
||||
adapter = prefs.flexeraScrubFromHome.getAdapter(),
|
||||
)
|
||||
}
|
||||
// No background colour or opacity here: the drawer is transparent with white text, and a
|
||||
// light panel behind white text would make the list unreadable.
|
||||
|
||||
@@ -308,7 +308,9 @@ class LawnchairLauncher : QuickstepLauncher() {
|
||||
|
||||
override fun createTouchControllers(): Array<TouchController> {
|
||||
val verticalSwipeController = VerticalSwipeTouchController(this, gestureController)
|
||||
return arrayOf<TouchController>(verticalSwipeController) + super.createTouchControllers()
|
||||
// Flexera Launcher: scrub from home (PRD AD-10) gets the first look at touches on the alphabet's edge.
|
||||
val edgeScrubController = app.flexera.drawer.FlexeraEdgeScrubController(this)
|
||||
return arrayOf<TouchController>(edgeScrubController, verticalSwipeController) + super.createTouchControllers()
|
||||
}
|
||||
|
||||
override fun handleHomeTap() {
|
||||
|
||||
@@ -217,6 +217,9 @@ class PreferenceManager @Inject constructor(
|
||||
// Flexera Launcher: which screen edge the drawer's wave alphabet sits on (PRD AD-15).
|
||||
val flexeraAlphabetOnLeft = BoolPref("pref_flexeraAlphabetOnLeft", false, recreate)
|
||||
|
||||
// Flexera Launcher: whether a slide along the home screen's edge opens the drawer at a letter (PRD AD-10).
|
||||
val flexeraScrubFromHome = BoolPref("pref_flexeraScrubFromHome", false)
|
||||
|
||||
override fun close() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
+9
-3
@@ -117,11 +117,17 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
protected boolean canInterceptTouch(MotionEvent ev) {
|
||||
mIsTrackpadSwipe = isTrackpadMotionEvent(ev);
|
||||
mLauncher.setCanShowAllAppsEducationView(!mIsTrackpadSwipe);
|
||||
if (!mIsTrackpadSwipe && DisplayController.getNavigationMode(mLauncher)
|
||||
== THREE_BUTTONS) {
|
||||
boolean threeButtons = !mIsTrackpadSwipe
|
||||
&& DisplayController.getNavigationMode(mLauncher) == THREE_BUTTONS;
|
||||
// Flexera Launcher: with three-button navigation this controller used to stand aside
|
||||
// completely. It now still takes the ordinary swipe up from the home screen, so that
|
||||
// swipe up and hold (PRD AD-17) works in that mode too. Everything else is left to the
|
||||
// controllers after it, as before.
|
||||
if (threeButtons && !mLauncher.isInState(NORMAL)) {
|
||||
return false;
|
||||
}
|
||||
mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
|
||||
// The buttons own the navigation bar, so a touch never counts as starting on it.
|
||||
mDidTouchStartInNavBar = !threeButtons && (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
|
||||
boolean isOneHandedModeActive = (SystemUiProxy.INSTANCE.get(mLauncher)
|
||||
.getLastSystemUiStateFlags() & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
|
||||
// Reset touch slop multiplier to default 1.0f if one-handed-mode is not active
|
||||
|
||||
@@ -121,6 +121,12 @@ public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageInd
|
||||
*/
|
||||
public static int getTabWidth(Context context, int totalWidth) {
|
||||
DeviceProfile grid = ActivityContext.lookupContext(context).getDeviceProfile();
|
||||
// Flexera Launcher: the one-column drawer list has no left and right icon to span. The
|
||||
// grid maths below would make the tabs and the work info card one icon wide, so line
|
||||
// them up with the search bar instead.
|
||||
if (grid.numShownAllAppsColumns == app.flexera.drawer.FlexeraDrawer.COLUMNS) {
|
||||
return totalWidth - 2 * app.flexera.drawer.FlexeraDrawer.rowStartPadding(context);
|
||||
}
|
||||
int iconPadding = totalWidth / grid.numShownAllAppsColumns
|
||||
- grid.getAllAppsProfile().getIconSizePx();
|
||||
return totalWidth - iconPadding;
|
||||
|
||||
Reference in new issue
Block a user